/*
 * This library is part of OpenCms -
 * the Open Source Content Management System
 *
 * Copyright (c) Alkacon Software GmbH & Co. KG (http://www.alkacon.com)
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * For further information about Alkacon Software GmbH & Co. KG, please see the
 * company website: http://www.alkacon.com
 *
 * For further information about OpenCms, please see the
 * project website: http://www.opencms.org
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

package org.opencms.db;

import org.opencms.ade.publish.CmsTooManyPublishResourcesException;
import org.opencms.configuration.CmsConfigurationManager;
import org.opencms.configuration.CmsParameterConfiguration;
import org.opencms.configuration.CmsSystemConfiguration;
import org.opencms.db.generic.CmsPublishHistoryCleanupFilter;
import org.opencms.db.generic.CmsUserDriver;
import org.opencms.db.log.CmsLogEntry;
import org.opencms.db.log.CmsLogEntryType;
import org.opencms.db.log.CmsLogFilter;
import org.opencms.db.timing.CmsDefaultProfilingHandler;
import org.opencms.db.timing.CmsProfilingInvocationHandler;
import org.opencms.db.urlname.CmsUrlNameMappingEntry;
import org.opencms.db.urlname.CmsUrlNameMappingFilter;
import org.opencms.db.userpublishlist.A_CmsLogPublishListConverter;
import org.opencms.db.userpublishlist.CmsLogPublishListConverterAllUsers;
import org.opencms.db.userpublishlist.CmsLogPublishListConverterCurrentUser;
import org.opencms.file.CmsDataAccessException;
import org.opencms.file.CmsFile;
import org.opencms.file.CmsFolder;
import org.opencms.file.CmsGroup;
import org.opencms.file.CmsObject;
import org.opencms.file.CmsProject;
import org.opencms.file.CmsProperty;
import org.opencms.file.CmsPropertyDefinition;
import org.opencms.file.CmsRequestContext;
import org.opencms.file.CmsResource;
import org.opencms.file.CmsResourceFilter;
import org.opencms.file.CmsUser;
import org.opencms.file.CmsUserSearchParameters;
import org.opencms.file.CmsVfsException;
import org.opencms.file.CmsVfsResourceAlreadyExistsException;
import org.opencms.file.CmsVfsResourceNotFoundException;
import org.opencms.file.I_CmsResource;
import org.opencms.file.history.CmsHistoryFile;
import org.opencms.file.history.CmsHistoryFolder;
import org.opencms.file.history.CmsHistoryPrincipal;
import org.opencms.file.history.CmsHistoryProject;
import org.opencms.file.history.I_CmsHistoryResource;
import org.opencms.file.types.CmsResourceTypeFolder;
import org.opencms.file.types.CmsResourceTypeJsp;
import org.opencms.file.types.I_CmsResourceType;
import org.opencms.flex.CmsFlexRequestContextInfo;
import org.opencms.gwt.shared.alias.CmsAliasImportResult;
import org.opencms.gwt.shared.alias.CmsAliasImportStatus;
import org.opencms.gwt.shared.alias.CmsAliasMode;
import org.opencms.i18n.CmsLocaleManager;
import org.opencms.i18n.CmsMessageContainer;
import org.opencms.jsp.CmsJspNavBuilder;
import org.opencms.lock.CmsLock;
import org.opencms.lock.CmsLockException;
import org.opencms.lock.CmsLockFilter;
import org.opencms.lock.CmsLockManager;
import org.opencms.lock.CmsLockType;
import org.opencms.main.CmsEvent;
import org.opencms.main.CmsException;
import org.opencms.main.CmsIllegalArgumentException;
import org.opencms.main.CmsIllegalStateException;
import org.opencms.main.CmsInitException;
import org.opencms.main.CmsLog;
import org.opencms.main.CmsMultiException;
import org.opencms.main.I_CmsEventListener;
import org.opencms.main.OpenCms;
import org.opencms.module.CmsModule;
import org.opencms.monitor.CmsMemoryMonitor;
import org.opencms.publish.CmsPublishEngine;
import org.opencms.publish.CmsPublishJobInfoBean;
import org.opencms.publish.CmsPublishReport;
import org.opencms.relations.CmsCategoryService;
import org.opencms.relations.CmsLink;
import org.opencms.relations.CmsRelation;
import org.opencms.relations.CmsRelationFilter;
import org.opencms.relations.CmsRelationSystemValidator;
import org.opencms.relations.CmsRelationType;
import org.opencms.relations.CmsRelationType.CopyBehavior;
import org.opencms.relations.I_CmsLinkParseable;
import org.opencms.report.CmsLogReport;
import org.opencms.report.I_CmsReport;
import org.opencms.security.CmsAccessControlEntry;
import org.opencms.security.CmsAccessControlList;
import org.opencms.security.CmsAuthentificationException;
import org.opencms.security.CmsOrganizationalUnit;
import org.opencms.security.CmsPasswordEncryptionException;
import org.opencms.security.CmsPermissionSet;
import org.opencms.security.CmsPermissionSetCustom;
import org.opencms.security.CmsPrincipal;
import org.opencms.security.CmsRole;
import org.opencms.security.CmsSecurityException;
import org.opencms.security.I_CmsPermissionHandler;
import org.opencms.security.I_CmsPrincipal;
import org.opencms.site.CmsSiteMatcher;
import org.opencms.util.CmsFileUtil;
import org.opencms.util.CmsStringUtil;
import org.opencms.util.CmsUUID;
import org.opencms.util.PrintfFormat;
import org.opencms.workflow.CmsDefaultWorkflowManager;
import org.opencms.workplace.threads.A_CmsProgressThread;

import java.lang.reflect.Proxy;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.ListIterator;
import java.util.Locale;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.TreeSet;
import java.util.concurrent.ConcurrentMap;
import java.util.function.Predicate;
import java.util.regex.Pattern;
import java.util.regex.PatternSyntaxException;
import java.util.stream.Collectors;

import org.apache.commons.logging.Log;

import com.google.common.collect.ArrayListMultimap;
import com.google.common.collect.Maps;

/**
 * The OpenCms driver manager.<p>
 *
 * @since 6.0.0
 */
public final class CmsDriverManager implements I_CmsEventListener {

    /**
     * The comparator used for comparing url name mapping entries by date.<p>
     */
    class UrlNameMappingComparator implements Comparator<CmsUrlNameMappingEntry> {

        /**
         * @see java.util.Comparator#compare(java.lang.Object, java.lang.Object)
         */
        public int compare(CmsUrlNameMappingEntry o1, CmsUrlNameMappingEntry o2) {

            long date1 = o1.getDateChanged();
            long date2 = o2.getDateChanged();
            if (date1 < date2) {
                return -1;
            }
            if (date1 > date2) {
                return +1;
            }
            return 0;
        }
    }

    /**
     * Enumeration class for the mode parameter in the
     * {@link CmsDriverManager#readChangedResourcesInsideProject(CmsDbContext, CmsUUID, CmsReadChangedProjectResourceMode)}
     * method.<p>
     */
    private static class CmsReadChangedProjectResourceMode {

        /**
         * Default constructor.<p>
         */
        protected CmsReadChangedProjectResourceMode() {

            // noop
        }
    }

    /** Attribute for signaling to the user driver that a specific OU should be initialized by fillDefaults. */
    public static final String ATTR_INIT_OU = "INIT_OU";

    /** Attribute login. */
    public static final String ATTRIBUTE_LOGIN = "A_LOGIN";

    /** Cache key for all properties. */
    public static final String CACHE_ALL_PROPERTIES = "_CAP_";

    /**
     * Values indicating changes of a resource,
     * ordered according to the scope of the change.
     */
    /** Value to indicate a change in access control entries of a resource. */
    public static final int CHANGED_ACCESSCONTROL = 1;

    /** Value to indicate a content change. */
    public static final int CHANGED_CONTENT = 16;

    /** Value to indicate a change in the lastmodified settings of a resource. */
    public static final int CHANGED_LASTMODIFIED = 4;

    /** Value to indicate a project change. */
    public static final int CHANGED_PROJECT = 32;

    /** Value to indicate a change in the resource data. */
    public static final int CHANGED_RESOURCE = 8;

    /** Value to indicate a change in the availability timeframe. */
    public static final int CHANGED_TIMEFRAME = 2;

    /** "cache" string in the configuration-file. */
    public static final String CONFIGURATION_CACHE = "cache";

    /** "db" string in the configuration-file. */
    public static final String CONFIGURATION_DB = "db";

    /** "driver.history" string in the configuration-file. */
    public static final String CONFIGURATION_HISTORY = "driver.history";

    /** "driver.project" string in the configuration-file. */
    public static final String CONFIGURATION_PROJECT = "driver.project";

    /** "subscription.vfs" string in the configuration file. */
    public static final String CONFIGURATION_SUBSCRIPTION = "driver.subscription";

    /** "driver.user" string in the configuration-file. */
    public static final String CONFIGURATION_USER = "driver.user";

    /** "driver.vfs" string in the configuration-file. */
    public static final String CONFIGURATION_VFS = "driver.vfs";

    /** DBC attribute key needed to fix publishing behavior involving siblings. */
    public static final String KEY_CHANGED_AND_DELETED = "changedAndDeleted";

    /** The vfs path of the loast and found folder. */
    public static final String LOST_AND_FOUND_FOLDER = "/system/lost-found";

    /** The maximum length of a VFS resource path. */
    public static final int MAX_VFS_RESOURCE_PATH_LENGTH = 512;

    /** Key for indicating no changes. */
    public static final int NOTHING_CHANGED = 0;

    /** Name of the configuration parameter to enable/disable logging to the CMS_LOG table. */
    public static final String PARAM_LOG_TABLE_ENABLED = "log.table.enabled";

    /** Indicates to ignore the resource path when matching resources. */
    public static final String READ_IGNORE_PARENT = null;

    /** Indicates to ignore the time value. */
    public static final long READ_IGNORE_TIME = 0L;

    /** Indicates to ignore the resource type when matching resources. */
    public static final int READ_IGNORE_TYPE = -1;

    /** Indicates to match resources NOT having the given state. */
    public static final int READMODE_EXCLUDE_STATE = 8;

    /** Indicates to match immediate children only. */
    public static final int READMODE_EXCLUDE_TREE = 1;

    /** Indicates to match resources NOT having the given type. */
    public static final int READMODE_EXCLUDE_TYPE = 4;

    /** Mode for reading project resources from the db. */
    public static final int READMODE_IGNORESTATE = 0;

    /** Indicates to match resources in given project only. */
    public static final int READMODE_INCLUDE_PROJECT = 2;

    /** Indicates to match all successors. */
    public static final int READMODE_INCLUDE_TREE = 0;

    /** Mode for reading project resources from the db. */
    public static final int READMODE_MATCHSTATE = 1;

    /** Indicates if only file resources should be read. */
    public static final int READMODE_ONLY_FILES = 128;

    /** Indicates if only folder resources should be read. */
    public static final int READMODE_ONLY_FOLDERS = 64;

    /** Mode for reading project resources from the db. */
    public static final int READMODE_UNMATCHSTATE = 2;

    /** Prefix char for temporary files in the VFS. */
    public static final String TEMP_FILE_PREFIX = "~";

    /** Key to indicate complete update. */
    public static final int UPDATE_ALL = 3;

    /** Key to indicate update of resource record. */
    public static final int UPDATE_RESOURCE = 4;

    /** Key to indicate update of last modified project reference. */
    public static final int UPDATE_RESOURCE_PROJECT = 6;

    /** Key to indicate update of resource state. */
    public static final int UPDATE_RESOURCE_STATE = 1;

    /** Key to indicate update of resource state including the content date. */
    public static final int UPDATE_RESOURCE_STATE_CONTENT = 7;

    /** Key to indicate update of structure record. */
    public static final int UPDATE_STRUCTURE = 5;

    /** Key to indicate update of structure state. */
    public static final int UPDATE_STRUCTURE_STATE = 2;

    /** Map of pools defined in opencms.properties. */
    protected static ConcurrentMap<String, CmsDbPoolV11> m_pools = Maps.newConcurrentMap();

    /** The log object for this class. */
    private static final Log LOG = CmsLog.getLog(CmsDriverManager.class);

    /** Constant mode parameter to read all files and folders in the {@link #readChangedResourcesInsideProject(CmsDbContext, CmsUUID, CmsReadChangedProjectResourceMode)}} method. */
    private static final CmsReadChangedProjectResourceMode RCPRM_FILES_AND_FOLDERS_MODE = new CmsReadChangedProjectResourceMode();

    /** Constant mode parameter to read all files and folders in the {@link #readChangedResourcesInsideProject(CmsDbContext, CmsUUID, CmsReadChangedProjectResourceMode)}} method. */
    private static final CmsReadChangedProjectResourceMode RCPRM_FILES_ONLY_MODE = new CmsReadChangedProjectResourceMode();

    /** Constant mode parameter to read all files and folders in the {@link #readChangedResourcesInsideProject(CmsDbContext, CmsUUID, CmsReadChangedProjectResourceMode)}} method. */
    private static final CmsReadChangedProjectResourceMode RCPRM_FOLDERS_ONLY_MODE = new CmsReadChangedProjectResourceMode();

    /** The history driver. */
    private I_CmsHistoryDriver m_historyDriver;

    /** The HTML link validator. */
    private CmsRelationSystemValidator m_htmlLinkValidator;

    /** The class used for cache key generation. */
    private I_CmsCacheKey m_keyGenerator;

    /** The lock manager. */
    private CmsLockManager m_lockManager;

    /** The log entry cache. */
    private List<CmsLogEntry> m_log = new ArrayList<CmsLogEntry>();

    /** Local reference to the memory monitor to avoid multiple lookups through the OpenCms singleton. */
    private CmsMemoryMonitor m_monitor;

    /** The project driver. */
    private I_CmsProjectDriver m_projectDriver;

    /** The the configuration read from the <code>opencms.properties</code> file. */
    private CmsParameterConfiguration m_propertyConfiguration;

    /** the publish engine. */
    private CmsPublishEngine m_publishEngine;

    /** Object used for synchronizing updates to the user publish list. */
    private Object m_publishListUpdateLock = new Object();

    /** The security manager (for access checks). */
    private CmsSecurityManager m_securityManager;

    /** The sql manager. */
    private CmsSqlManager m_sqlManager;

    /** The subscription driver. */
    private I_CmsSubscriptionDriver m_subscriptionDriver;

    /** The user driver. */
    private I_CmsUserDriver m_userDriver;

    /** The VFS driver. */
    private I_CmsVfsDriver m_vfsDriver;

    /**
     * Private constructor, initializes some required member variables.<p>
     */
    private CmsDriverManager() {

        // intentionally left blank
    }

    /**
     * Reads the required configurations from the opencms.properties file and creates
     * the various drivers to access the cms resources.<p>
     *
     * The initialization process of the driver manager and its drivers is split into
     * the following phases:
     * <ul>
     * <li>the database pool configuration is read</li>
     * <li>a plain and empty driver manager instance is created</li>
     * <li>an instance of each driver is created</li>
     * <li>the driver manager is passed to each driver during initialization</li>
     * <li>finally, the driver instances are passed to the driver manager during initialization</li>
     * </ul>
     *
     * @param configurationManager the configuration manager
     * @param securityManager the security manager
     * @param runtimeInfoFactory the initialized OpenCms runtime info factory
     * @param publishEngine the publish engine
     *
     * @return CmsDriverManager the instantiated driver manager
     * @throws CmsInitException if the driver manager couldn't be instantiated
     */
    public static CmsDriverManager newInstance(
        CmsConfigurationManager configurationManager,
        CmsSecurityManager securityManager,
        I_CmsDbContextFactory runtimeInfoFactory,
        CmsPublishEngine publishEngine)
    throws CmsInitException {

        // read the opencms.properties from the configuration
        CmsParameterConfiguration config = configurationManager.getConfiguration();

        CmsDriverManager driverManager = null;
        try {
            // create a driver manager instance
            driverManager = new CmsDriverManager();
            if (CmsLog.INIT.isInfoEnabled()) {
                CmsLog.INIT.info(Messages.get().getBundle().key(Messages.INIT_DRIVER_MANAGER_START_PHASE1_0));
            }
            if (runtimeInfoFactory == null) {
                throw new CmsInitException(
                    org.opencms.main.Messages.get().container(org.opencms.main.Messages.ERR_CRITICAL_NO_DB_CONTEXT_0));
            }
        } catch (Exception exc) {
            CmsMessageContainer message = Messages.get().container(Messages.LOG_ERR_DRIVER_MANAGER_START_0);
            if (LOG.isFatalEnabled()) {
                LOG.fatal(message.key(), exc);
            }
            throw new CmsInitException(message, exc);
        }

        // store the configuration
        driverManager.m_propertyConfiguration = config;

        // set the security manager
        driverManager.m_securityManager = securityManager;

        // set the lock manager
        driverManager.m_lockManager = new CmsLockManager(driverManager);

        // create and set the sql manager
        driverManager.m_sqlManager = new CmsSqlManager(driverManager);

        // set the publish engine
        driverManager.m_publishEngine = publishEngine;

        if (CmsLog.INIT.isInfoEnabled()) {
            CmsLog.INIT.info(Messages.get().getBundle().key(Messages.INIT_DRIVER_MANAGER_START_PHASE2_0));
        }

        // read the pool names to initialize
        List<String> driverPoolNames = config.getList(CmsDriverManager.CONFIGURATION_DB + ".pools");
        if (CmsLog.INIT.isInfoEnabled()) {
            String names = "";
            for (String name : driverPoolNames) {
                names += name + " ";
            }
            CmsLog.INIT.info(Messages.get().getBundle().key(Messages.INIT_DRIVER_MANAGER_START_POOLS_1, names));
        }

        // initialize each pool
        for (String name : driverPoolNames) {
            driverManager.newPoolInstance(config, name);
        }

        // initialize the runtime info factory with the generated driver manager
        runtimeInfoFactory.initialize(driverManager);

        if (CmsLog.INIT.isInfoEnabled()) {
            CmsLog.INIT.info(Messages.get().getBundle().key(Messages.INIT_DRIVER_MANAGER_START_PHASE3_0));
        }

        // store the access objects
        CmsDbContext dbc = runtimeInfoFactory.getDbContext();
        driverManager.m_vfsDriver = (I_CmsVfsDriver)driverManager.createDriver(
            dbc,
            configurationManager,
            config,
            CONFIGURATION_VFS,
            ".vfs.driver");
        dbc.clear();

        dbc = runtimeInfoFactory.getDbContext();
        driverManager.m_userDriver = (I_CmsUserDriver)driverManager.createDriver(
            dbc,
            configurationManager,
            config,
            CONFIGURATION_USER,
            ".user.driver");
        dbc.clear();

        dbc = runtimeInfoFactory.getDbContext();
        driverManager.m_projectDriver = (I_CmsProjectDriver)driverManager.createDriver(
            dbc,
            configurationManager,
            config,
            CONFIGURATION_PROJECT,
            ".project.driver");
        dbc.clear();

        dbc = runtimeInfoFactory.getDbContext();
        driverManager.m_historyDriver = (I_CmsHistoryDriver)driverManager.createDriver(
            dbc,
            configurationManager,
            config,
            CONFIGURATION_HISTORY,
            ".history.driver");
        dbc.clear();

        dbc = runtimeInfoFactory.getDbContext();
        try {
            // we wrap this in a try-catch because otherwise it would fail during the update
            // process, since the subscription driver configuration does not exist at that point.
            driverManager.m_subscriptionDriver = (I_CmsSubscriptionDriver)driverManager.createDriver(
                dbc,
                configurationManager,
                config,
                CONFIGURATION_SUBSCRIPTION,
                ".subscription.driver");
        } catch (IndexOutOfBoundsException npe) {
            LOG.warn("Could not instantiate subscription driver!");
            LOG.warn(npe.getLocalizedMessage(), npe);
        }
        dbc.clear();

        // register the driver manager for required events
        org.opencms.main.OpenCms.addCmsEventListener(
            driverManager,
            new int[] {
                I_CmsEventListener.EVENT_UPDATE_EXPORTS,
                I_CmsEventListener.EVENT_CLEAR_CACHES,
                I_CmsEventListener.EVENT_CLEAR_PRINCIPAL_CACHES,
                I_CmsEventListener.EVENT_USER_MODIFIED,
                I_CmsEventListener.EVENT_PUBLISH_PROJECT});

        // return the configured driver manager
        return driverManager;
    }

    /**
     * Adds an alias entry.<p>
     *
     * @param dbc the database context
     * @param project the current project
     * @param alias the alias to add
     *
     * @throws CmsException if something goes wrong
     */
    public void addAlias(CmsDbContext dbc, CmsProject project, CmsAlias alias) throws CmsException {

        I_CmsVfsDriver vfsDriver = getVfsDriver(dbc);
        vfsDriver.insertAlias(dbc, project, alias);
    }

    /**
     * Adds a new relation to the given resource.<p>
     *
     * @param dbc the database context
     * @param resource the resource to add the relation to
     * @param target the target of the relation
     * @param type the type of the relation
     * @param importCase if importing relations
     *
     * @throws CmsException if something goes wrong
     */
    public void addRelationToResource(
        CmsDbContext dbc,
        CmsResource resource,
        CmsResource target,
        CmsRelationType type,
        boolean importCase)
    throws CmsException {

        if (type.isDefinedInContent()) {
            throw new CmsIllegalArgumentException(
                Messages.get().container(
                    Messages.ERR_ADD_RELATION_IN_CONTENT_3,
                    dbc.removeSiteRoot(resource.getRootPath()),
                    dbc.removeSiteRoot(target.getRootPath()),
                    type.getLocalizedName(dbc.getRequestContext().getLocale())));
        }
        CmsRelation relation = new CmsRelation(resource, target, type);
        getVfsDriver(dbc).createRelation(dbc, dbc.currentProject().getUuid(), relation);
        if (!importCase) {
            // log it
            log(
                dbc,
                new CmsLogEntry(
                    dbc,
                    resource.getStructureId(),
                    CmsLogEntryType.RESOURCE_ADD_RELATION,
                    new String[] {relation.getSourcePath(), relation.getTargetPath()}),
                false);
            // touch the resource
            setDateLastModified(dbc, resource, System.currentTimeMillis());
        }
    }

    /**
     * Adds a resource to the given organizational unit.<p>
     *
     * @param dbc the current db context
     * @param orgUnit the organizational unit to add the resource to
     * @param resource the resource that is to be added to the organizational unit
     *
     * @throws CmsException if something goes wrong
     *
     * @see org.opencms.security.CmsOrgUnitManager#addResourceToOrgUnit(CmsObject, String, String)
     * @see org.opencms.security.CmsOrgUnitManager#addResourceToOrgUnit(CmsObject, String, String)
     */
    public void addResourceToOrgUnit(CmsDbContext dbc, CmsOrganizationalUnit orgUnit, CmsResource resource)
    throws CmsException {

        m_monitor.flushCache(CmsMemoryMonitor.CacheType.HAS_ROLE, CmsMemoryMonitor.CacheType.ROLE_LIST);
        getUserDriver(dbc).addResourceToOrganizationalUnit(dbc, orgUnit, resource);
    }

    /**
     * Adds a user to a group.<p>
     *
     * @param dbc the current database context
     * @param username the name of the user that is to be added to the group
     * @param groupname the name of the group
     * @param readRoles if reading roles or groups
     *
     * @throws CmsException if operation was not successful
     * @throws CmsDbEntryNotFoundException if the given user or the given group was not found
     *
     * @see #removeUserFromGroup(CmsDbContext, String, String, boolean)
     */
    public void addUserToGroup(CmsDbContext dbc, String username, String groupname, boolean readRoles)
    throws CmsException, CmsDbEntryNotFoundException {

        //check if group exists
        CmsGroup group = readGroup(dbc, groupname);
        if (group == null) {
            // the group does not exists
            throw new CmsDbEntryNotFoundException(Messages.get().container(Messages.ERR_UNKNOWN_GROUP_1, groupname));
        }
        if (group.isVirtual() && !readRoles) {
            String roleName = CmsRole.valueOf(group).getGroupName();
            if (!userInGroup(dbc, username, roleName, true)) {
                addUserToGroup(dbc, username, roleName, true);
                return;
            }
        }
        if (group.isVirtual()) {
            // this is an hack to prevent unlimited recursive calls
            readRoles = false;
        }
        if ((readRoles && !group.isRole()) || (!readRoles && group.isRole())) {
            // we want a role but we got a group, or the other way
            throw new CmsDbEntryNotFoundException(Messages.get().container(Messages.ERR_UNKNOWN_GROUP_1, groupname));
        }
        if (userInGroup(dbc, username, groupname, readRoles)) {
            // the user is already member of the group
            return;
        }
        //check if the user exists
        CmsUser user = readUser(dbc, username);
        if (user == null) {
            // the user does not exists
            throw new CmsDbEntryNotFoundException(Messages.get().container(Messages.ERR_UNKNOWN_USER_1, username));
        }

        // if adding an user to a role
        if (readRoles) {
            CmsRole role = CmsRole.valueOf(group);
            // a role can only be set if the user has the given role
            m_securityManager.checkRole(dbc, role);
            // now we check if we already have the role
            if (m_securityManager.hasRole(dbc, user, role)) {
                // do nothing
                return;
            }
            // and now we need to remove all possible child-roles
            List<CmsRole> children = role.getChildren(true);
            Iterator<CmsGroup> itUserGroups = getGroupsOfUser(
                dbc,
                username,
                group.getOuFqn(),
                true,
                true,
                true,
                dbc.getRequestContext().getRemoteAddress()).iterator();
            while (itUserGroups.hasNext()) {
                CmsGroup roleGroup = itUserGroups.next();
                if (children.contains(CmsRole.valueOf(roleGroup))) {
                    // remove only child roles
                    removeUserFromGroup(dbc, username, roleGroup.getName(), true);
                }
            }
            // update virtual groups
            Iterator<CmsGroup> it = getVirtualGroupsForRole(dbc, role).iterator();
            while (it.hasNext()) {
                CmsGroup virtualGroup = it.next();
                // here we say readroles = true, to prevent an unlimited recursive calls
                addUserToGroup(dbc, username, virtualGroup.getName(), true);
            }
        }

        //add this user to the group
        getUserDriver(dbc).createUserInGroup(dbc, user.getId(), group.getId());

        // flush the cache
        if (readRoles) {
            m_monitor.flushCache(CmsMemoryMonitor.CacheType.HAS_ROLE, CmsMemoryMonitor.CacheType.ROLE_LIST);
        }
        m_monitor.flushCache(CmsMemoryMonitor.CacheType.USERGROUPS, CmsMemoryMonitor.CacheType.USER_LIST);

        if (!dbc.getProjectId().isNullUUID() && !CmsProject.ONLINE_PROJECT_ID.equals(dbc.getProjectId())) {
            // user modified event is not needed
            return;
        }
        // fire user modified event
        Map<String, Object> eventData = new HashMap<String, Object>();
        eventData.put(I_CmsEventListener.KEY_USER_ID, user.getId().toString());
        eventData.put(I_CmsEventListener.KEY_USER_NAME, user.getName());
        eventData.put(I_CmsEventListener.KEY_USER_ID, user.getId().toString());
        eventData.put(I_CmsEventListener.KEY_GROUP_NAME, group.getName());
        eventData.put(
            I_CmsEventListener.KEY_USER_ACTION,
            I_CmsEventListener.VALUE_USER_MODIFIED_ACTION_ADD_USER_TO_GROUP);
        OpenCms.fireCmsEvent(new CmsEvent(I_CmsEventListener.EVENT_USER_MODIFIED, eventData));
    }

    /**
     * Changes the lock of a resource to the current user,
     * that is "steals" the lock from another user.<p>
     *
     * @param dbc the current database context
     * @param resource the resource to change the lock for
     * @param lockType the new lock type to set
     *
     * @throws CmsException if something goes wrong
     * @throws CmsSecurityException if something goes wrong
     *
     *
     * @see CmsObject#changeLock(String)
     * @see I_CmsResourceType#changeLock(CmsObject, CmsSecurityManager, CmsResource)
     *
     * @see CmsSecurityManager#hasPermissions(CmsRequestContext, CmsResource, CmsPermissionSet, boolean, CmsResourceFilter)
     */
    public void changeLock(CmsDbContext dbc, CmsResource resource, CmsLockType lockType)
    throws CmsException, CmsSecurityException {

        // get the current lock
        CmsLock currentLock = getLock(dbc, resource);
        // check if the resource is locked at all
        if (currentLock.getEditionLock().isUnlocked() && currentLock.getSystemLock().isUnlocked()) {
            throw new CmsLockException(
                Messages.get().container(
                    Messages.ERR_CHANGE_LOCK_UNLOCKED_RESOURCE_1,
                    dbc.getRequestContext().getSitePath(resource)));
        } else if ((lockType == CmsLockType.EXCLUSIVE)
            && currentLock.isExclusiveOwnedInProjectBy(dbc.currentUser(), dbc.currentProject())) {
                // the current lock requires no change
                return;
            }

        // duplicate logic from CmsSecurityManager#hasPermissions() because lock state can't be ignored
        // if another user has locked the file, the current user can never get WRITE permissions with the default check
        int denied = 0;

        // check if the current user is vfs manager
        boolean canIgnorePermissions = m_securityManager.hasRoleForResource(
            dbc,
            dbc.currentUser(),
            CmsRole.VFS_MANAGER,
            resource);
        // if the resource type is jsp
        // write is only allowed for developers
        if (!canIgnorePermissions && (CmsResourceTypeJsp.isJsp(resource))) {
            if (!m_securityManager.hasRoleForResource(dbc, dbc.currentUser(), CmsRole.VFS_MANAGER, resource)) {
                denied |= CmsPermissionSet.PERMISSION_WRITE;
            }
        }
        CmsPermissionSetCustom permissions;
        if (canIgnorePermissions) {
            // if the current user is administrator, anything is allowed
            permissions = new CmsPermissionSetCustom(~0);
        } else {
            // otherwise, get the permissions from the access control list
            permissions = getPermissions(dbc, resource, dbc.currentUser());
        }
        // revoke the denied permissions
        permissions.denyPermissions(denied);
        // now check if write permission is granted
        if ((CmsPermissionSet.ACCESS_WRITE.getPermissions()
            & permissions.getPermissions()) != CmsPermissionSet.ACCESS_WRITE.getPermissions()) {
            // check failed, throw exception
            m_securityManager.checkPermissions(
                dbc.getRequestContext(),
                resource,
                CmsPermissionSet.ACCESS_WRITE,
                I_CmsPermissionHandler.PERM_DENIED);
        }
        // if we got here write permission is granted on the target

        // remove the old lock
        m_lockManager.removeResource(dbc, resource, true, lockType.isSystem());
        // apply the new lock
        lockResource(dbc, resource, lockType);
    }

    /**
     * Returns a list with all sub resources of a given folder that have set the given property,
     * matching the current property's value with the given old value and replacing it by a given new value.<p>
     *
     * @param dbc the current database context
     * @param resource the resource on which property definition values are changed
     * @param propertyDefinition the name of the propertydefinition to change the value
     * @param oldValue the old value of the propertydefinition
     * @param newValue the new value of the propertydefinition
     * @param recursive if true, change the property value on the resource and recursively all property values on
     *                     sub-resources (only for folders)
     * @return a list with the <code>{@link CmsResource}</code>'s where the property value has been changed
     *
     * @throws CmsVfsException for now only when the search for the oldvalue failed.
     * @throws CmsException if operation was not successful
     */
    public List<CmsResource> changeResourcesInFolderWithProperty(
        CmsDbContext dbc,
        CmsResource resource,
        String propertyDefinition,
        String oldValue,
        String newValue,
        boolean recursive)
    throws CmsVfsException, CmsException {

        CmsResourceFilter filter = CmsResourceFilter.IGNORE_EXPIRATION;
        // collect the resources to look up
        List<CmsResource> resources = new ArrayList<CmsResource>();
        if (recursive) {
            // read the files in the folder
            resources = readResourcesWithProperty(dbc, resource, propertyDefinition, null, filter);
            // add the folder itself
            resources.add(resource);
        } else {
            resources.add(resource);
        }

        Pattern oldPattern;
        try {
            // remove the place holder if available
            String tmpOldValue = oldValue;
            if (tmpOldValue.contains(CmsStringUtil.PLACEHOLDER_START)
                && tmpOldValue.contains(CmsStringUtil.PLACEHOLDER_END)) {
                tmpOldValue = tmpOldValue.replace(CmsStringUtil.PLACEHOLDER_START, "");
                tmpOldValue = tmpOldValue.replace(CmsStringUtil.PLACEHOLDER_END, "");
            }
            // compile regular expression pattern
            oldPattern = Pattern.compile(tmpOldValue);
        } catch (PatternSyntaxException e) {
            throw new CmsVfsException(
                Messages.get().container(
                    Messages.ERR_CHANGE_RESOURCES_IN_FOLDER_WITH_PROP_4,
                    new Object[] {propertyDefinition, oldValue, newValue, resource.getRootPath()}),
                e);
        }

        List<CmsResource> changedResources = new ArrayList<CmsResource>(resources.size());
        // create permission set and filter to check each resource
        CmsPermissionSet perm = CmsPermissionSet.ACCESS_WRITE;
        for (int i = 0; i < resources.size(); i++) {
            // loop through found resources and check property values
            CmsResource res = resources.get(i);
            // check resource state and permissions
            try {
                m_securityManager.checkPermissions(dbc, res, perm, true, filter);
            } catch (Exception e) {
                // resource is deleted or not writable for current user
                continue;
            }
            CmsProperty property = readPropertyObject(dbc, res, propertyDefinition, false);
            String propertyValue = property.getValue();
            boolean changed = false;
            if ((propertyValue != null) && oldPattern.matcher(propertyValue).matches()) {
                // apply the place holder content
                String tmpNewValue = CmsStringUtil.transformValues(oldValue, newValue, propertyValue);
                // change structure value
                property.setStructureValue(tmpNewValue);
                changed = true;
            }
            if (changed) {
                // write property object if something has changed
                writePropertyObject(dbc, res, property);
                changedResources.add(res);
            }
        }
        return changedResources;
    }

    /**
     * Changes the resource flags of a resource.<p>
     *
     * The resource flags are used to indicate various "special" conditions
     * for a resource. Most notably, the "internal only" setting which signals
     * that a resource can not be directly requested with it's URL.<p>
     *
     * @param dbc the current database context
     * @param resource the resource to change the flags for
     * @param flags the new resource flags for this resource
     *
     * @throws CmsException if something goes wrong
     *
     * @see CmsObject#chflags(String, int)
     * @see I_CmsResourceType#chflags(CmsObject, CmsSecurityManager, CmsResource, int)
     */
    public void chflags(CmsDbContext dbc, CmsResource resource, int flags) throws CmsException {

        // must operate on a clone to ensure resource is not modified in case permissions are not granted
        CmsResource clone = (CmsResource)resource.clone();
        clone.setFlags(flags);
        // log it
        log(
            dbc,
            new CmsLogEntry(
                dbc,
                resource.getStructureId(),
                CmsLogEntryType.RESOURCE_FLAGS,
                new String[] {resource.getRootPath()}),
            false);
        // write it
        writeResource(dbc, clone);
    }

    /**
     * Changes the resource type of a resource.<p>
     *
     * OpenCms handles resources according to the resource type,
     * not the file suffix. This is e.g. why a JSP in OpenCms can have the
     * suffix ".html" instead of ".jsp" only. Changing the resource type
     * makes sense e.g. if you want to make a plain text file a JSP resource,
     * or a binary file an image, etc.<p>
     *
     * @param dbc the current database context
     * @param resource the resource to change the type for
     * @param type the new resource type for this resource
     *
     * @throws CmsException if something goes wrong
     *
     * @see CmsObject#chtype(String, int)
     * @see I_CmsResourceType#chtype(CmsObject, CmsSecurityManager, CmsResource, int)
     */
    @SuppressWarnings({"javadoc", "deprecation"})
    public void chtype(CmsDbContext dbc, CmsResource resource, int type) throws CmsException {

        // must operate on a clone to ensure resource is not modified in case permissions are not granted
        CmsResource clone = (CmsResource)resource.clone();
        I_CmsResourceType newType = OpenCms.getResourceManager().getResourceType(type);
        clone.setType(newType.getTypeId());
        // log it
        log(
            dbc,
            new CmsLogEntry(
                dbc,
                resource.getStructureId(),
                CmsLogEntryType.RESOURCE_TYPE,
                new String[] {resource.getRootPath()}),
            false);
        // write it
        writeResource(dbc, clone);
    }

    /**
     * Cleans up the publish history entries according to the given filter.
     *
     * @param dbc the database context
     * @param filter the filter
     * @return the number of cleaned up rows
     * @throws CmsDataAccessException if something goes wrong
     */
    public int cleanupPublishHistory(CmsDbContext dbc, CmsPublishHistoryCleanupFilter filter)
    throws CmsDataAccessException {

        int result = m_projectDriver.cleanupPublishHistory(dbc, filter);
        if (filter.getMode() == CmsPublishHistoryCleanupFilter.Mode.single) {
            OpenCms.getMemoryMonitor().cachePublishedResources(filter.getHistoryId().toString(), null);
        } else {
            OpenCms.getMemoryMonitor().flushCache(CmsMemoryMonitor.CacheType.PUBLISHED_RESOURCES);
        }
        return result;
    }

    /**
     * @see org.opencms.main.I_CmsEventListener#cmsEvent(org.opencms.main.CmsEvent)
     */
    public void cmsEvent(CmsEvent event) {

        if (LOG.isDebugEnabled()) {
            LOG.debug(Messages.get().getBundle().key(Messages.LOG_CMS_EVENT_1, new Integer(event.getType())));
        }

        I_CmsReport report;
        CmsDbContext dbc;

        switch (event.getType()) {

            case I_CmsEventListener.EVENT_UPDATE_EXPORTS:
                dbc = (CmsDbContext)event.getData().get(I_CmsEventListener.KEY_DBCONTEXT);
                updateExportPoints(dbc);
                break;

            case I_CmsEventListener.EVENT_PUBLISH_PROJECT:
                CmsUUID publishHistoryId = new CmsUUID((String)event.getData().get(I_CmsEventListener.KEY_PUBLISHID));
                report = (I_CmsReport)event.getData().get(I_CmsEventListener.KEY_REPORT);
                dbc = (CmsDbContext)event.getData().get(I_CmsEventListener.KEY_DBCONTEXT);
                m_monitor.clearCache();
                writeExportPoints(dbc, report, publishHistoryId);
                break;

            case I_CmsEventListener.EVENT_CLEAR_CACHES:
                m_monitor.clearCache();
                break;
            case I_CmsEventListener.EVENT_CLEAR_PRINCIPAL_CACHES:
            case I_CmsEventListener.EVENT_USER_MODIFIED:
                m_monitor.clearPrincipalsCache();
                break;
            default:
                // noop
        }
    }

    /**
     * Copies the access control entries of a given resource to a destination resource.<p>
     *
     * Already existing access control entries of the destination resource are removed.<p>
     *
     * @param dbc the current database context
     * @param source the resource to copy the access control entries from
     * @param destination the resource to which the access control entries are copied
     * @param updateLastModifiedInfo if true, user and date "last modified" information on the target resource will be updated
     *
     * @throws CmsException if something goes wrong
     */
    public void copyAccessControlEntries(
        CmsDbContext dbc,
        CmsResource source,
        CmsResource destination,
        boolean updateLastModifiedInfo)
    throws CmsException {

        // get the entries to copy
        ListIterator<CmsAccessControlEntry> aceList = getUserDriver(
            dbc).readAccessControlEntries(dbc, dbc.currentProject(), source.getResourceId(), false).listIterator();

        // remove the current entries from the destination
        getUserDriver(dbc).removeAccessControlEntries(dbc, dbc.currentProject(), destination.getResourceId());

        // now write the new entries
        while (aceList.hasNext()) {
            CmsAccessControlEntry ace = aceList.next();
            getUserDriver(dbc).createAccessControlEntry(
                dbc,
                dbc.currentProject(),
                destination.getResourceId(),
                ace.getPrincipal(),
                ace.getPermissions().getAllowedPermissions(),
                ace.getPermissions().getDeniedPermissions(),
                ace.getFlags());
        }

        // log it
        log(
            dbc,
            new CmsLogEntry(
                dbc,
                destination.getStructureId(),
                CmsLogEntryType.RESOURCE_PERMISSIONS,
                new String[] {destination.getRootPath()}),
            false);

        // update the "last modified" information
        if (updateLastModifiedInfo) {
            setDateLastModified(dbc, destination, destination.getDateLastModified());
        }

        // clear the cache
        m_monitor.clearAccessControlListCache();

        // fire a resource modification event
        Map<String, Object> data = new HashMap<String, Object>(2);
        data.put(I_CmsEventListener.KEY_RESOURCE, destination);
        data.put(I_CmsEventListener.KEY_CHANGE, new Integer(CHANGED_ACCESSCONTROL));
        OpenCms.fireCmsEvent(new CmsEvent(I_CmsEventListener.EVENT_RESOURCE_MODIFIED, data));
    }

    /**
     * Copies a resource.<p>
     *
     * You must ensure that the destination path is an absolute, valid and
     * existing VFS path. Relative paths from the source are currently not supported.<p>
     *
     * In case the target resource already exists, it is overwritten with the
     * source resource.<p>
     *
     * The <code>siblingMode</code> parameter controls how to handle siblings
     * during the copy operation.
     * Possible values for this parameter are:
     * <ul>
     * <li><code>{@link org.opencms.file.CmsResource#COPY_AS_NEW}</code></li>
     * <li><code>{@link org.opencms.file.CmsResource#COPY_AS_SIBLING}</code></li>
     * <li><code>{@link org.opencms.file.CmsResource#COPY_PRESERVE_SIBLING}</code></li>
     * </ul><p>
     *
     * @param dbc the current database context
     * @param source the resource to copy
     * @param destination the name of the copy destination with complete path
     * @param siblingMode indicates how to handle siblings during copy
     *
     * @throws CmsException if something goes wrong
     * @throws CmsIllegalArgumentException if the <code>source</code> argument is <code>null</code>
     *
     * @see CmsObject#copyResource(String, String, CmsResource.CmsResourceCopyMode)
     * @see I_CmsResourceType#copyResource(CmsObject, CmsSecurityManager, CmsResource, String, CmsResource.CmsResourceCopyMode)
     */
    public void copyResource(
        CmsDbContext dbc,
        CmsResource source,
        String destination,
        CmsResource.CmsResourceCopyMode siblingMode)
    throws CmsException, CmsIllegalArgumentException {

        // check the sibling mode to see if this resource has to be copied as a sibling
        boolean copyAsSibling = false;

        // siblings of folders are not supported
        if (!source.isFolder()) {
            // if the "copy as sibling" mode is used, set the flag to true
            if (siblingMode == CmsResource.COPY_AS_SIBLING) {
                copyAsSibling = true;
            }
            // if the mode is "preserve siblings", we have to check the sibling counter
            if (siblingMode == CmsResource.COPY_PRESERVE_SIBLING) {
                if (source.getSiblingCount() > 1) {
                    copyAsSibling = true;
                }
            }
        }

        // read the source properties
        List<CmsProperty> properties = readPropertyObjects(dbc, source, false);

        if (copyAsSibling) {
            // create a sibling of the source file at the destination
            createSibling(dbc, source, destination, properties);
            // after the sibling is created the copy operation is finished
            return;
        }

        // prepare the content if required
        byte[] content = null;
        if (source.isFile()) {
            if (source instanceof CmsFile) {
                // resource already is a file
                content = ((CmsFile)source).getContents();
            }
            if ((content == null) || (content.length < 1)) {
                // no known content yet - read from database
                content = getVfsDriver(dbc).readContent(dbc, dbc.currentProject().getUuid(), source.getResourceId());
            }
        }

        // determine destination folder
        String destinationFoldername = CmsResource.getParentFolder(destination);

        // read the destination folder (will also check read permissions)
        CmsFolder destinationFolder = m_securityManager.readFolder(
            dbc,
            destinationFoldername,
            CmsResourceFilter.IGNORE_EXPIRATION);

        // no further permission check required here, will be done in createResource()

        // set user and creation time stamps
        long currentTime = System.currentTimeMillis();
        long dateLastModified;
        CmsUUID userLastModified;
        if (source.isFolder()) {
            // folders always get a new date and user when they are copied
            dateLastModified = currentTime;
            userLastModified = dbc.currentUser().getId();
        } else {
            // files keep the date and user last modified from the source
            dateLastModified = source.getDateLastModified();
            userLastModified = source.getUserLastModified();
        }

        // check the resource flags
        int flags = source.getFlags();
        if (source.isLabeled()) {
            // reset "labeled" link flag for new resource
            flags &= ~CmsResource.FLAG_LABELED;
        }

        // create the new resource
        CmsResource newResource = new CmsResource(
            new CmsUUID(),
            new CmsUUID(),
            destination,
            source.getTypeId(),
            source.isFolder(),
            flags,
            dbc.currentProject().getUuid(),
            CmsResource.STATE_NEW,
            currentTime,
            dbc.currentUser().getId(),
            dateLastModified,
            userLastModified,
            source.getDateReleased(),
            source.getDateExpired(),
            1,
            source.getLength(),
            source.getDateContent(),
            source.getVersion()); // version number does not matter since it will be computed later

        // trigger "is touched" state on resource (will ensure modification date is kept unchanged)
        newResource.setDateLastModified(dateLastModified);

        // log it
        log(
            dbc,
            new CmsLogEntry(
                dbc,
                newResource.getStructureId(),
                CmsLogEntryType.RESOURCE_COPIED,
                new String[] {newResource.getRootPath()}),
            false);

        // create the resource
        newResource = createResource(dbc, destination, newResource, content, properties, false);
        // copy relations
        copyRelations(dbc, source, newResource);

        // copy the access control entries to the created resource
        copyAccessControlEntries(dbc, source, newResource, false);

        // clear the cache
        m_monitor.clearAccessControlListCache();

        List<CmsResource> modifiedResources = new ArrayList<CmsResource>();
        modifiedResources.add(source);
        modifiedResources.add(newResource);
        modifiedResources.add(destinationFolder);
        OpenCms.fireCmsEvent(
            new CmsEvent(
                I_CmsEventListener.EVENT_RESOURCE_COPIED,
                Collections.<String, Object> singletonMap(I_CmsEventListener.KEY_RESOURCES, modifiedResources)));
    }

    /**
     * Copies a resource to the current project of the user.<p>
     *
     * @param dbc the current database context
     * @param resource the resource to apply this operation to
     *
     * @throws CmsException if something goes wrong
     *
     * @see CmsObject#copyResourceToProject(String)
     * @see I_CmsResourceType#copyResourceToProject(CmsObject, CmsSecurityManager, CmsResource)
     */
    public void copyResourceToProject(CmsDbContext dbc, CmsResource resource) throws CmsException {

        // copy the resource to the project only if the resource is not already in the project
        if (!isInsideCurrentProject(dbc, resource.getRootPath())) {
            // check if there are already any subfolders of this resource
            I_CmsProjectDriver projectDriver = getProjectDriver(dbc);
            if (resource.isFolder()) {
                List<String> projectResources = projectDriver.readProjectResources(dbc, dbc.currentProject());
                for (int i = 0; i < projectResources.size(); i++) {
                    String resname = projectResources.get(i);
                    if (resname.startsWith(resource.getRootPath())) {
                        // delete the existing project resource first
                        projectDriver.deleteProjectResource(dbc, dbc.currentProject().getUuid(), resname);
                    }
                }
            }
            try {
                projectDriver.createProjectResource(dbc, dbc.currentProject().getUuid(), resource.getRootPath());
            } catch (CmsException exc) {
                // if the subfolder exists already - all is ok
            } finally {
                m_monitor.flushCache(CmsMemoryMonitor.CacheType.PROJECT_RESOURCES);

                OpenCms.fireCmsEvent(
                    new CmsEvent(
                        I_CmsEventListener.EVENT_PROJECT_MODIFIED,
                        Collections.<String, Object> singletonMap("project", dbc.currentProject())));
            }
        }
    }

    /**
     * Counts the locked resources in this project.<p>
     *
     * @param project the project to count the locked resources in
     *
     * @return the amount of locked resources in this project
     */
    public int countLockedResources(CmsProject project) {

        // count locks
        return m_lockManager.countExclusiveLocksInProject(project);
    }

    /**
     * Add a new group to the Cms.<p>
     *
     * Only the admin can do this.
     * Only users, which are in the group "administrators" are granted.<p>
     *
     * @param dbc the current database context
     * @param id the id of the new group
     * @param name the name of the new group
     * @param description the description for the new group
     * @param flags the flags for the new group
     * @param parent the name of the parent group (or <code>null</code>)
     *
     * @return new created group
     *
     * @throws CmsException if the creation of the group failed
     * @throws CmsIllegalArgumentException if the length of the given name was below 1
     */
    public CmsGroup createGroup(CmsDbContext dbc, CmsUUID id, String name, String description, int flags, String parent)
    throws CmsIllegalArgumentException, CmsException {

        // check the group name
        OpenCms.getValidationHandler().checkGroupName(CmsOrganizationalUnit.getSimpleName(name));
        // trim the name
        name = name.trim();

        // check the OU
        readOrganizationalUnit(dbc, CmsOrganizationalUnit.getParentFqn(name));

        // get the id of the parent group if necessary
        if (CmsStringUtil.isNotEmpty(parent)) {
            CmsGroup parentGroup = readGroup(dbc, parent);
            if (!parentGroup.isRole()
                && !CmsOrganizationalUnit.getParentFqn(parent).equals(CmsOrganizationalUnit.getParentFqn(name))) {
                throw new CmsDataAccessException(
                    Messages.get().container(
                        Messages.ERR_PARENT_GROUP_MUST_BE_IN_SAME_OU_3,
                        CmsOrganizationalUnit.getSimpleName(name),
                        CmsOrganizationalUnit.getParentFqn(name),
                        parent));
            }
        }

        // create the group
        CmsGroup group = getUserDriver(dbc).createGroup(dbc, id, name, description, flags, parent);

        // if the group is in fact a role, initialize it
        if (group.isVirtual()) {
            // get all users that have the given role
            String groupname = CmsRole.valueOf(group).getGroupName();
            Iterator<CmsUser> it = getUsersOfGroup(dbc, groupname, true, false, true).iterator();
            while (it.hasNext()) {
                CmsUser user = it.next();
                // put them in the new group
                addUserToGroup(dbc, user.getName(), group.getName(), true);
            }
        }

        // put it into the cache
        m_monitor.cacheGroup(group);

        if (!dbc.getProjectId().isNullUUID()) {
            // group modified event is not needed
            return group;
        }
        // fire group modified event
        Map<String, Object> eventData = new HashMap<String, Object>();
        eventData.put(I_CmsEventListener.KEY_GROUP_NAME, group.getName());
        eventData.put(I_CmsEventListener.KEY_GROUP_ID, group.getId().toString());
        eventData.put(I_CmsEventListener.KEY_USER_ACTION, I_CmsEventListener.VALUE_GROUP_MODIFIED_ACTION_CREATE);
        OpenCms.fireCmsEvent(new CmsEvent(I_CmsEventListener.EVENT_GROUP_MODIFIED, eventData));

        // return it
        return group;
    }

    /**
     * Creates a new organizational unit.<p>
     *
     * @param dbc the current db context
     * @param ouFqn the fully qualified name of the new organizational unit
     * @param description the description of the new organizational unit
     * @param flags the flags for the new organizational unit
     * @param resource the first associated resource
     *
     * @return a <code>{@link CmsOrganizationalUnit}</code> object representing
     *          the newly created organizational unit
     *
     * @throws CmsException if operation was not successful
     *
     * @see org.opencms.security.CmsOrgUnitManager#createOrganizationalUnit(CmsObject, String, String, int, String)
     */
    public CmsOrganizationalUnit createOrganizationalUnit(
        CmsDbContext dbc,
        String ouFqn,
        String description,
        int flags,
        CmsResource resource)
    throws CmsException {

        // normal case
        CmsOrganizationalUnit parent = readOrganizationalUnit(dbc, CmsOrganizationalUnit.getParentFqn(ouFqn));
        String name = CmsOrganizationalUnit.getSimpleName(ouFqn);
        if (name.endsWith(CmsOrganizationalUnit.SEPARATOR)) {
            name = name.substring(0, name.length() - 1);
        }

        // check the name
        CmsResource.checkResourceName(name);

        // trim the name
        name = name.trim();

        // check the description
        if (CmsStringUtil.isEmptyOrWhitespaceOnly(description)) {
            throw new CmsIllegalArgumentException(Messages.get().container(Messages.ERR_BAD_OU_DESCRIPTION_EMPTY_0));
        }

        // create the organizational unit
        CmsOrganizationalUnit orgUnit = getUserDriver(dbc).createOrganizationalUnit(
            dbc,
            name,
            description,
            flags,
            parent,
            resource != null ? resource.getRootPath() : null);
        // put the new created org unit into the cache
        m_monitor.cacheOrgUnit(orgUnit);

        // flush relevant caches
        m_monitor.clearPrincipalsCache();
        m_monitor.flushCache(CmsMemoryMonitor.CacheType.PROPERTY, CmsMemoryMonitor.CacheType.PROPERTY_LIST);

        // create a publish list for the 'virtual' publish event
        CmsResource ouRes = readResource(
            dbc,
            CmsUserDriver.ORGUNIT_BASE_FOLDER + orgUnit.getName(),
            CmsResourceFilter.DEFAULT);
        CmsPublishList pl = new CmsPublishList(ouRes, false);
        pl.add(ouRes, false);

        getProjectDriver(dbc).writePublishHistory(
            dbc,
            pl.getPublishHistoryId(),
            new CmsPublishedResource(ouRes, -1, CmsResourceState.STATE_NEW));

        // fire the 'virtual' publish event
        Map<String, Object> eventData = new HashMap<String, Object>();
        eventData.put(I_CmsEventListener.KEY_PUBLISHID, pl.getPublishHistoryId().toString());
        eventData.put(I_CmsEventListener.KEY_PROJECTID, dbc.currentProject().getUuid());
        eventData.put(I_CmsEventListener.KEY_DBCONTEXT, dbc);
        CmsEvent afterPublishEvent = new CmsEvent(I_CmsEventListener.EVENT_PUBLISH_PROJECT, eventData);
        OpenCms.fireCmsEvent(afterPublishEvent);

        if (!dbc.getProjectId().isNullUUID()) {
            // OU modified event is not needed
            return orgUnit;
        }

        // fire OU modified event
        Map<String, Object> event2Data = new HashMap<String, Object>();
        event2Data.put(I_CmsEventListener.KEY_OU_NAME, orgUnit.getName());
        event2Data.put(I_CmsEventListener.KEY_OU_ID, orgUnit.getId().toString());
        event2Data.put(I_CmsEventListener.KEY_USER_ACTION, I_CmsEventListener.VALUE_OU_MODIFIED_ACTION_CREATE);
        OpenCms.fireCmsEvent(new CmsEvent(I_CmsEventListener.EVENT_OU_MODIFIED, event2Data));

        // return it
        return orgUnit;
    }

    /**
     * Creates a project.<p>
     *
     * @param dbc the current database context
     * @param name the name of the project to create
     * @param description the description of the project
     * @param groupname the project user group to be set
     * @param managergroupname the project manager group to be set
     * @param projecttype the type of the project
     *
     * @return the created project
     *
     * @throws CmsIllegalArgumentException if the chosen <code>name</code> is already used
     *         by the online project, or if the name is not valid
     * @throws CmsException if something goes wrong
     */
    public CmsProject createProject(
        CmsDbContext dbc,
        String name,
        String description,
        String groupname,
        String managergroupname,
        CmsProject.CmsProjectType projecttype)
    throws CmsIllegalArgumentException, CmsException {

        if (CmsProject.ONLINE_PROJECT_NAME.equals(name)) {
            throw new CmsIllegalArgumentException(
                Messages.get().container(
                    Messages.ERR_CREATE_PROJECT_ONLINE_PROJECT_NAME_1,
                    CmsProject.ONLINE_PROJECT_NAME));
        }
        // check the name
        CmsProject.checkProjectName(CmsOrganizationalUnit.getSimpleName(name));
        // check the ou
        readOrganizationalUnit(dbc, CmsOrganizationalUnit.getParentFqn(name));
        // read the needed groups from the cms
        CmsGroup group = readGroup(dbc, groupname);
        CmsGroup managergroup = readGroup(dbc, managergroupname);

        return getProjectDriver(dbc).createProject(
            dbc,
            new CmsUUID(),
            dbc.currentUser(),
            group,
            managergroup,
            name,
            description,
            projecttype.getDefaultFlags(),
            projecttype);
    }

    /**
     * Creates a property definition.<p>
     *
     * Property definitions are valid for all resource types.<p>
     *
     * @param dbc the current database context
     * @param name the name of the property definition to create
     *
     * @return the created property definition
     *
     * @throws CmsException if something goes wrong
     */
    public CmsPropertyDefinition createPropertyDefinition(CmsDbContext dbc, String name) throws CmsException {

        CmsPropertyDefinition propertyDefinition = null;

        name = name.trim();
        // validate the property name
        CmsPropertyDefinition.checkPropertyName(name);
        // TODO: make the type a parameter
        try {
            try {
                propertyDefinition = getVfsDriver(dbc).readPropertyDefinition(
                    dbc,
                    name,
                    dbc.currentProject().getUuid());
            } catch (CmsException e) {
                propertyDefinition = getVfsDriver(dbc).createPropertyDefinition(
                    dbc,
                    dbc.currentProject().getUuid(),
                    name,
                    CmsPropertyDefinition.TYPE_NORMAL);
            }

            try {
                getVfsDriver(dbc).readPropertyDefinition(dbc, name, CmsProject.ONLINE_PROJECT_ID);
            } catch (CmsException e) {
                getVfsDriver(dbc).createPropertyDefinition(
                    dbc,
                    CmsProject.ONLINE_PROJECT_ID,
                    name,
                    CmsPropertyDefinition.TYPE_NORMAL);
            }

            try {
                getHistoryDriver(dbc).readPropertyDefinition(dbc, name);
            } catch (CmsException e) {
                getHistoryDriver(dbc).createPropertyDefinition(dbc, name, CmsPropertyDefinition.TYPE_NORMAL);
            }
        } finally {

            // fire an event that a property of a resource has been deleted
            OpenCms.fireCmsEvent(
                new CmsEvent(
                    I_CmsEventListener.EVENT_PROPERTY_DEFINITION_CREATED,
                    Collections.<String, Object> singletonMap("propertyDefinition", propertyDefinition)));

        }

        return propertyDefinition;
    }

    /**
     * Creates a new publish job.<p>
     *
     * @param dbc the current database context
     * @param publishJob the publish job to create
     *
     * @throws CmsException if something goes wrong
     */
    public void createPublishJob(CmsDbContext dbc, CmsPublishJobInfoBean publishJob) throws CmsException {

        getProjectDriver(dbc).createPublishJob(dbc, publishJob);
    }

    /**
     * Creates a new resource with the provided content and properties.<p>
     *
     * The <code>content</code> parameter may be <code>null</code> if the resource id
     * already exists. If so, the created resource will be a sibling of the existing
     * resource, the existing content will remain unchanged.<p>
     *
     * This is used during file import for import of siblings as the
     * <code>manifest.xml</code> only contains one binary copy per file.<p>
     *
     * If the resource id exists but the <code>content</code> is not <code>null</code>,
     * the created resource will be made a sibling of the existing resource,
     * and both will share the new content.<p>
     *
     * @param dbc the current database context
     * @param resourcePath the name of the resource to create (full path)
     * @param resource the new resource to create
     * @param content the content for the new resource
     * @param properties the properties for the new resource
     * @param importCase if <code>true</code>, signals that this operation is done while
     *                      importing resource, causing different lock behavior and
     *                      potential "lost and found" usage
     *
     * @return the created resource
     *
     * @throws CmsException if something goes wrong
     */
    public CmsResource createResource(
        CmsDbContext dbc,
        String resourcePath,
        CmsResource resource,
        byte[] content,
        List<CmsProperty> properties,
        boolean importCase)
    throws CmsException {

        CmsResource newResource = null;
        if (resource.isFolder()) {
            resourcePath = CmsFileUtil.addTrailingSeparator(resourcePath);
        }

        try {
            synchronized (this) {
                // need to provide the parent folder id for resource creation
                String parentFolderName = CmsResource.getParentFolder(resourcePath);
                CmsResource parentFolder = readFolder(dbc, parentFolderName, CmsResourceFilter.IGNORE_EXPIRATION);

                CmsLock parentLock = getLock(dbc, parentFolder);
                // it is not allowed to create a resource in a folder locked by other user
                if (!parentLock.isUnlocked() && !parentLock.isOwnedBy(dbc.currentUser())) {
                    // one exception is if the admin user tries to create a temporary resource
                    if (!CmsResource.getName(resourcePath).startsWith(TEMP_FILE_PREFIX)
                        || !m_securityManager.hasRole(dbc, dbc.currentUser(), CmsRole.ROOT_ADMIN)) {
                        throw new CmsLockException(
                            Messages.get().container(
                                Messages.ERR_CREATE_RESOURCE_PARENT_LOCK_1,
                                dbc.removeSiteRoot(resourcePath)));
                    }
                }
                if (CmsResourceTypeJsp.isJsp(resource)) {
                    // security check when trying to create a new jsp file
                    m_securityManager.checkRoleForResource(dbc, CmsRole.VFS_MANAGER, parentFolder);
                }

                // check import configuration of "lost and found" folder
                boolean useLostAndFound = importCase && !OpenCms.getImportExportManager().overwriteCollidingResources();

                // check if the resource already exists by name
                CmsResource currentResourceByName = null;
                try {
                    currentResourceByName = readResource(dbc, resourcePath, CmsResourceFilter.ALL);
                } catch (CmsVfsResourceNotFoundException e) {
                    // if the resource does exist, we have to check the id later to decide what to do
                }

                // check if the resource already exists by id
                try {
                    CmsResource currentResourceById = readResource(
                        dbc,
                        resource.getStructureId(),
                        CmsResourceFilter.ALL);
                    // it is not allowed to import resources when there is already a resource with the same id but different path
                    if (!currentResourceById.getRootPath().equals(resourcePath)) {
                        throw new CmsVfsResourceAlreadyExistsException(
                            Messages.get().container(
                                Messages.ERR_RESOURCE_WITH_ID_ALREADY_EXISTS_3,
                                dbc.removeSiteRoot(resourcePath),
                                dbc.removeSiteRoot(currentResourceById.getRootPath()),
                                currentResourceById.getStructureId()));
                    }
                } catch (CmsVfsResourceNotFoundException e) {
                    // if the resource does exist, we have to check the id later to decide what to do
                }

                // check the permissions
                if (currentResourceByName == null) {
                    // resource does not exist - check parent folder
                    m_securityManager.checkPermissions(
                        dbc,
                        parentFolder,
                        CmsPermissionSet.ACCESS_WRITE,
                        false,
                        CmsResourceFilter.IGNORE_EXPIRATION);
                } else {
                    // resource already exists - check existing resource
                    m_securityManager.checkPermissions(
                        dbc,
                        currentResourceByName,
                        CmsPermissionSet.ACCESS_WRITE,
                        !importCase,
                        CmsResourceFilter.ALL);
                }

                // now look for the resource by name
                if (currentResourceByName != null) {
                    boolean overwrite = true;
                    if (currentResourceByName.getState().isDeleted()) {
                        if (!currentResourceByName.isFolder()) {
                            // if a non-folder resource was deleted it's treated like a new resource
                            overwrite = false;
                        }
                    } else {
                        if (!importCase) {
                            // direct "overwrite" of a resource is possible only during import,
                            // or if the resource has been deleted
                            throw new CmsVfsResourceAlreadyExistsException(
                                org.opencms.db.generic.Messages.get().container(
                                    org.opencms.db.generic.Messages.ERR_RESOURCE_WITH_NAME_ALREADY_EXISTS_1,
                                    dbc.removeSiteRoot(resource.getRootPath())));
                        }
                        // the resource already exists
                        if (!resource.isFolder()
                            && useLostAndFound
                            && (!currentResourceByName.getResourceId().equals(resource.getResourceId()))) {
                            // semantic change: the current resource is moved to L&F and the imported resource will overwrite the old one
                            // will leave the resource with state deleted,
                            // but it does not matter, since the state will be set later again
                            moveToLostAndFound(dbc, currentResourceByName, false);
                        }
                    }
                    if (!overwrite) {
                        // lock the resource, will throw an exception if not lockable
                        lockResource(dbc, currentResourceByName, CmsLockType.EXCLUSIVE);

                        // trigger createResource instead of writeResource
                        currentResourceByName = null;
                    }
                }
                // if null, create new resource, if not null write resource
                CmsResource overwrittenResource = currentResourceByName;

                // extract the name (without path)
                String targetName = CmsResource.getName(resourcePath);

                int contentLength;

                // modify target name and content length in case of folder creation
                if (resource.isFolder()) {
                    // folders never have any content
                    contentLength = -1;
                    // must cut of trailing '/' for folder creation (or name check fails)
                    if (CmsResource.isFolder(targetName)) {
                        targetName = targetName.substring(0, targetName.length() - 1);
                    }
                } else {
                    // otherwise ensure content and content length are set correctly
                    if (content != null) {
                        // if a content is provided, in each case the length is the length of this content
                        contentLength = content.length;
                    } else if (overwrittenResource != null) {
                        // we have no content, but an already existing resource - length remains unchanged
                        contentLength = overwrittenResource.getLength();
                    } else {
                        // we have no content - length is used as set in the resource
                        contentLength = resource.getLength();
                    }
                }

                // check if the target name is valid (forbidden chars etc.),
                // if not throw an exception
                // must do this here since targetName is modified in folder case (see above)
                CmsResource.checkResourceName(targetName);

                // set structure and resource ids as given
                CmsUUID structureId = resource.getStructureId();
                CmsUUID resourceId = resource.getResourceId();

                // decide which structure id to use
                if (overwrittenResource != null) {
                    // resource exists, re-use existing ids
                    structureId = overwrittenResource.getStructureId();
                }
                if (structureId.isNullUUID()) {
                    // need a new structure id
                    structureId = new CmsUUID();
                }

                // decide which resource id to use
                if (overwrittenResource != null) {
                    // if we are overwriting we have to assure the resource id is the same
                    resourceId = overwrittenResource.getResourceId();
                }
                if (resourceId.isNullUUID()) {
                    // need a new resource id
                    resourceId = new CmsUUID();
                }

                try {
                    // check online resource
                    CmsResource onlineResource = getVfsDriver(
                        dbc).readResource(dbc, CmsProject.ONLINE_PROJECT_ID, resourcePath, true);
                    // only allow to overwrite with different id if importing (createResource will set the right id)
                    try {
                        CmsResource offlineResource = getVfsDriver(dbc).readResource(
                            dbc,
                            dbc.currentProject().getUuid(),
                            onlineResource.getStructureId(),
                            true);
                        if (!offlineResource.getRootPath().equals(onlineResource.getRootPath())) {
                            throw new CmsVfsOnlineResourceAlreadyExistsException(
                                Messages.get().container(
                                    Messages.ERR_ONLINE_RESOURCE_EXISTS_2,
                                    dbc.removeSiteRoot(resourcePath),
                                    dbc.removeSiteRoot(offlineResource.getRootPath())));
                        }
                    } catch (CmsVfsResourceNotFoundException e) {
                        // there is no problem for now
                        // but should never happen
                        if (LOG.isErrorEnabled()) {
                            LOG.error(e.getLocalizedMessage(), e);
                        }
                    }
                } catch (CmsVfsResourceNotFoundException e) {
                    // ok, there is no online entry to worry about
                }

                // now create a resource object with all informations
                newResource = new CmsResource(
                    structureId,
                    resourceId,
                    resourcePath,
                    resource.getTypeId(),
                    resource.isFolder(),
                    resource.getFlags(),
                    dbc.currentProject().getUuid(),
                    resource.getState(),
                    resource.getDateCreated(),
                    resource.getUserCreated(),
                    resource.getDateLastModified(),
                    resource.getUserLastModified(),
                    resource.getDateReleased(),
                    resource.getDateExpired(),
                    1,
                    contentLength,
                    resource.getDateContent(),
                    resource.getVersion()); // version number does not matter since it will be computed later

                // ensure date is updated only if required
                if (resource.isTouched()) {
                    // this will trigger the internal "is touched" state on the new resource
                    newResource.setDateLastModified(resource.getDateLastModified());
                }

                if (resource.isFile()) {
                    // check if a sibling to the imported resource lies in a marked site
                    if (labelResource(dbc, resource, resourcePath, 2)) {
                        int flags = resource.getFlags();
                        flags |= CmsResource.FLAG_LABELED;
                        resource.setFlags(flags);
                    }
                    // ensure siblings don't overwrite existing resource records
                    if (content == null) {
                        newResource.setState(CmsResource.STATE_KEEP);
                    }
                }

                // delete all relations for the resource, before writing the content
                getVfsDriver(
                    dbc).deleteRelations(dbc, dbc.currentProject().getUuid(), newResource, CmsRelationFilter.TARGETS);
                if (overwrittenResource == null) {
                    CmsLock lock = getLock(dbc, newResource);
                    if (lock.getEditionLock().isExclusive()) {
                        unlockResource(dbc, newResource, true, false);
                    }
                    // resource does not exist.
                    newResource = getVfsDriver(
                        dbc).createResource(dbc, dbc.currentProject().getUuid(), newResource, content);
                } else {
                    // resource already exists.
                    // probably the resource is a merged page file that gets overwritten during import, or it gets
                    // overwritten by a copy operation. if so, the structure & resource state are not modified to changed.
                    int updateStates = (overwrittenResource.getState().isNew()
                    ? CmsDriverManager.NOTHING_CHANGED
                    : CmsDriverManager.UPDATE_ALL);
                    getVfsDriver(dbc).writeResource(dbc, dbc.currentProject().getUuid(), newResource, updateStates);

                    if ((content != null) && resource.isFile()) {
                        // also update file content if required
                        getVfsDriver(dbc).writeContent(dbc, newResource.getResourceId(), content);
                    }
                }

                // write the properties (internal operation, no events or duplicate permission checks)
                writePropertyObjects(dbc, newResource, properties, false);

                // lock the created resource
                try {
                    // if it is locked by another user (copied or moved resource) this lock should be preserved and
                    // the exception is OK: locks on created resources are a slave feature to original locks
                    lockResource(dbc, newResource, CmsLockType.EXCLUSIVE);
                } catch (CmsLockException cle) {
                    if (LOG.isDebugEnabled()) {
                        LOG.debug(
                            Messages.get().getBundle().key(
                                Messages.ERR_CREATE_RESOURCE_LOCK_1,
                                new Object[] {dbc.removeSiteRoot(newResource.getRootPath())}));
                    }
                }

                if (!importCase) {
                    log(
                        dbc,
                        new CmsLogEntry(
                            dbc,
                            newResource.getStructureId(),
                            CmsLogEntryType.RESOURCE_CREATED,
                            new String[] {resource.getRootPath()}),
                        false);
                } else {
                    log(
                        dbc,
                        new CmsLogEntry(
                            dbc,
                            newResource.getStructureId(),
                            CmsLogEntryType.RESOURCE_IMPORTED,
                            new String[] {resource.getRootPath()}),
                        false);
                }
            }
        } finally {
            // clear the internal caches
            m_monitor.clearAccessControlListCache();
            m_monitor.flushCache(CmsMemoryMonitor.CacheType.PROPERTY, CmsMemoryMonitor.CacheType.PROPERTY_LIST);

            if (newResource != null) {
                // fire an event that a new resource has been created
                OpenCms.fireCmsEvent(
                    new CmsEvent(
                        I_CmsEventListener.EVENT_RESOURCE_CREATED,
                        Collections.<String, Object> singletonMap(I_CmsEventListener.KEY_RESOURCE, newResource)));
            }
        }
        return newResource;
    }

    /**
     * Creates a new resource of the given resource type
     * with the provided content and properties.<p>
     *
     * If the provided content is null and the resource is not a folder,
     * the content will be set to an empty byte array.<p>
     *
     * @param dbc the current database context
     * @param resourcename the name of the resource to create (full path)
     * @param type the type of the resource to create
     * @param content the content for the new resource
     * @param properties the properties for the new resource
     *
     * @return the created resource
     *
     * @throws CmsException if something goes wrong
     * @throws CmsIllegalArgumentException if the <code>resourcename</code> argument is null or of length 0
     *
     * @see CmsObject#createResource(String, int, byte[], List)
     * @see CmsObject#createResource(String, int)
     * @see I_CmsResourceType#createResource(CmsObject, CmsSecurityManager, String, byte[], List)
     */
    @SuppressWarnings("javadoc")
    public CmsResource createResource(
        CmsDbContext dbc,
        String resourcename,
        int type,
        byte[] content,
        List<CmsProperty> properties)
    throws CmsException, CmsIllegalArgumentException {

        String targetName = resourcename;

        if (content == null) {
            // name based resource creation MUST have a content
            content = new byte[0];
        }
        int size;

        if (CmsFolder.isFolderType(type)) {
            // must cut of trailing '/' for folder creation
            if (CmsResource.isFolder(targetName)) {
                targetName = targetName.substring(0, targetName.length() - 1);
            }
            size = -1;
        } else {
            size = content.length;
        }

        // create a new resource
        CmsResource newResource = new CmsResource(
            CmsUUID.getNullUUID(), // uuids will be "corrected" later
            CmsUUID.getNullUUID(),
            targetName,
            type,
            CmsFolder.isFolderType(type),
            0,
            dbc.currentProject().getUuid(),
            CmsResource.STATE_NEW,
            0,
            dbc.currentUser().getId(),
            0,
            dbc.currentUser().getId(),
            CmsResource.DATE_RELEASED_DEFAULT,
            CmsResource.DATE_EXPIRED_DEFAULT,
            1,
            size,
            0, // version number does not matter since it will be computed later
            0); // content time will be corrected later

        return createResource(dbc, targetName, newResource, content, properties, false);
    }

    /**
     * Creates a new sibling of the source resource.<p>
     *
     * @param dbc the current database context
     * @param source the resource to create a sibling for
     * @param destination the name of the sibling to create with complete path
     * @param properties the individual properties for the new sibling
     *
     * @return the new created sibling
     *
     * @throws CmsException if something goes wrong
     *
     * @see CmsObject#createSibling(String, String, List)
     * @see I_CmsResourceType#createSibling(CmsObject, CmsSecurityManager, CmsResource, String, List)
     */
    public CmsResource createSibling(
        CmsDbContext dbc,
        CmsResource source,
        String destination,
        List<CmsProperty> properties)
    throws CmsException {

        if (source.isFolder()) {
            throw new CmsVfsException(Messages.get().container(Messages.ERR_VFS_FOLDERS_DONT_SUPPORT_SIBLINGS_0));
        }

        // determine destination folder and resource name
        String destinationFoldername = CmsResource.getParentFolder(destination);

        // read the destination folder (will also check read permissions)
        CmsFolder destinationFolder = readFolder(dbc, destinationFoldername, CmsResourceFilter.IGNORE_EXPIRATION);

        // no further permission check required here, will be done in createResource()

        // check the resource flags
        int flags = source.getFlags();
        if (labelResource(dbc, source, destination, 1)) {
            // set "labeled" link flag for new resource
            flags |= CmsResource.FLAG_LABELED;
        }

        // create the new resource
        CmsResource newResource = new CmsResource(
            new CmsUUID(),
            source.getResourceId(),
            destination,
            source.getTypeId(),
            source.isFolder(),
            flags,
            dbc.currentProject().getUuid(),
            CmsResource.STATE_KEEP,
            source.getDateCreated(), // ensures current resource record remains untouched
            source.getUserCreated(),
            source.getDateLastModified(),
            source.getUserLastModified(),
            source.getDateReleased(),
            source.getDateExpired(),
            source.getSiblingCount() + 1,
            source.getLength(),
            source.getDateContent(),
            source.getVersion()); // version number does not matter since it will be computed later

        // trigger "is touched" state on resource (will ensure modification date is kept unchanged)
        newResource.setDateLastModified(newResource.getDateLastModified());

        log(
            dbc,
            new CmsLogEntry(
                dbc,
                newResource.getStructureId(),
                CmsLogEntryType.RESOURCE_CLONED,
                new String[] {newResource.getRootPath()}),
            false);
        // create the resource (null content signals creation of sibling)
        newResource = createResource(dbc, destination, newResource, null, properties, false);

        // copy relations
        copyRelations(dbc, source, newResource);

        // clear the caches
        m_monitor.clearAccessControlListCache();

        List<CmsResource> modifiedResources = new ArrayList<CmsResource>();
        modifiedResources.add(source);
        modifiedResources.add(newResource);
        modifiedResources.add(destinationFolder);
        OpenCms.fireCmsEvent(
            new CmsEvent(
                I_CmsEventListener.EVENT_RESOURCES_AND_PROPERTIES_MODIFIED,
                Collections.<String, Object> singletonMap(I_CmsEventListener.KEY_RESOURCES, modifiedResources)));

        return newResource;
    }

    /**
     * Creates the project for the temporary workplace files.<p>
     *
     * @param dbc the current database context
     *
     * @return the created project for the temporary workplace files
     *
     * @throws CmsException if something goes wrong
     */
    public CmsProject createTempfileProject(CmsDbContext dbc) throws CmsException {

        // read the needed groups from the cms
        CmsGroup projectUserGroup = readGroup(dbc, dbc.currentProject().getGroupId());
        CmsGroup projectManagerGroup = readGroup(dbc, dbc.currentProject().getManagerGroupId());

        CmsProject tempProject = getProjectDriver(dbc).createProject(
            dbc,
            new CmsUUID(),
            dbc.currentUser(),
            projectUserGroup,
            projectManagerGroup,
            I_CmsProjectDriver.TEMP_FILE_PROJECT_NAME,
            Messages.get().getBundle(dbc.getRequestContext().getLocale()).key(
                Messages.GUI_WORKPLACE_TEMPFILE_PROJECT_DESC_0),
            CmsProject.PROJECT_FLAG_HIDDEN,
            CmsProject.PROJECT_TYPE_NORMAL);
        getProjectDriver(dbc).createProjectResource(dbc, tempProject.getUuid(), "/");

        OpenCms.fireCmsEvent(
            new CmsEvent(
                I_CmsEventListener.EVENT_PROJECT_MODIFIED,
                Collections.<String, Object> singletonMap("project", tempProject)));

        return tempProject;
    }

    /**
     * Creates a new user.<p>
     *
     * @param dbc the current database context
     * @param name the name for the new user
     * @param password the password for the new user
     * @param description the description for the new user
     * @param additionalInfos the additional infos for the user
     *
     * @return the created user
     *
     * @see CmsObject#createUser(String, String, String, Map)
     *
     * @throws CmsException if something goes wrong
     * @throws CmsIllegalArgumentException if the name for the user is not valid
     */
    public CmsUser createUser(
        CmsDbContext dbc,
        String name,
        String password,
        String description,
        Map<String, Object> additionalInfos)
    throws CmsException, CmsIllegalArgumentException {

        // no space before or after the name
        name = name.trim();
        // check the user name
        String userName = CmsOrganizationalUnit.getSimpleName(name);
        OpenCms.getValidationHandler().checkUserName(userName);
        if (CmsStringUtil.isEmptyOrWhitespaceOnly(userName)) {
            throw new CmsIllegalArgumentException(Messages.get().container(Messages.ERR_BAD_USER_1, userName));
        }
        // check the ou
        CmsOrganizationalUnit ou = readOrganizationalUnit(dbc, CmsOrganizationalUnit.getParentFqn(name));
        // check the password
        validatePassword(password);

        Map<String, Object> info = new HashMap<String, Object>();
        if (additionalInfos != null) {
            info.putAll(additionalInfos);
        }
        if (description != null) {
            info.put(CmsUserSettings.ADDITIONAL_INFO_DESCRIPTION, description);
        }
        int flags = 0;
        if (ou.hasFlagWebuser()) {
            flags += I_CmsPrincipal.FLAG_USER_WEBUSER;
        }
        CmsUser user = getUserDriver(dbc).createUser(
            dbc,
            new CmsUUID(),
            name,
            OpenCms.getPasswordHandler().digest(password),
            " ",
            " ",
            " ",
            0,
            I_CmsPrincipal.FLAG_ENABLED + flags,
            0,
            info);

        if (!dbc.getProjectId().isNullUUID()) {
            // user modified event is not needed
            return user;
        }
        // fire user modified event
        Map<String, Object> eventData = new HashMap<String, Object>();
        eventData.put(I_CmsEventListener.KEY_USER_ID, user.getId().toString());
        eventData.put(I_CmsEventListener.KEY_USER_ACTION, I_CmsEventListener.VALUE_USER_MODIFIED_ACTION_CREATE_USER);
        OpenCms.fireCmsEvent(new CmsEvent(I_CmsEventListener.EVENT_USER_MODIFIED, eventData));
        return user;
    }

    /**
     * Deletes aliases indicated by a filter.<p>
     *
     * @param dbc the current database context
     * @param project the current project
     * @param filter the filter which describes which aliases to delete
     *
     * @throws CmsException if something goes wrong
     */
    public void deleteAliases(CmsDbContext dbc, CmsProject project, CmsAliasFilter filter) throws CmsException {

        I_CmsVfsDriver vfsDriver = getVfsDriver(dbc);
        vfsDriver.deleteAliases(dbc, project, filter);
    }

    /**
     * Deletes all property values of a file or folder.<p>
     *
     * If there are no other siblings than the specified resource,
     * both the structure and resource property values get deleted.
     * If the specified resource has siblings, only the structure
     * property values get deleted.<p>
     *
     * @param dbc the current database context
     * @param resourcename the name of the resource for which all properties should be deleted
     *
     * @throws CmsException if operation was not successful
     */
    public void deleteAllProperties(CmsDbContext dbc, String resourcename) throws CmsException {

        CmsResource resource = null;
        List<CmsResource> resources = new ArrayList<CmsResource>();

        try {
            // read the resource
            resource = readResource(dbc, resourcename, CmsResourceFilter.IGNORE_EXPIRATION);

            // check the security
            m_securityManager.checkPermissions(
                dbc,
                resource,
                CmsPermissionSet.ACCESS_WRITE,
                false,
                CmsResourceFilter.ALL);

            // delete the property values
            if (resource.getSiblingCount() > 1) {
                // the resource has siblings- delete only the (structure) properties of this sibling
                getVfsDriver(dbc).deletePropertyObjects(
                    dbc,
                    dbc.currentProject().getUuid(),
                    resource,
                    CmsProperty.DELETE_OPTION_DELETE_STRUCTURE_VALUES);
                resources.addAll(readSiblings(dbc, resource, CmsResourceFilter.ALL));

            } else {
                // the resource has no other siblings- delete all (structure+resource) properties
                getVfsDriver(dbc).deletePropertyObjects(
                    dbc,
                    dbc.currentProject().getUuid(),
                    resource,
                    CmsProperty.DELETE_OPTION_DELETE_STRUCTURE_AND_RESOURCE_VALUES);
                resources.add(resource);
            }
        } finally {
            // clear the driver manager cache
            m_monitor.flushCache(CmsMemoryMonitor.CacheType.PROPERTY, CmsMemoryMonitor.CacheType.PROPERTY_LIST);

            // fire an event that all properties of a resource have been deleted
            OpenCms.fireCmsEvent(
                new CmsEvent(
                    I_CmsEventListener.EVENT_RESOURCES_AND_PROPERTIES_MODIFIED,
                    Collections.<String, Object> singletonMap(I_CmsEventListener.KEY_RESOURCES, resources)));
        }
    }

    /**
     * Deletes all entries in the published resource table.<p>
     *
     * @param dbc the current database context
     * @param linkType the type of resource deleted (0= non-paramter, 1=parameter)
     *
     * @throws CmsException if something goes wrong
     */
    public void deleteAllStaticExportPublishedResources(CmsDbContext dbc, int linkType) throws CmsException {

        getProjectDriver(dbc).deleteAllStaticExportPublishedResources(dbc, linkType);
    }

    /**
     * Deletes a group, where all permissions, users and children of the group
     * are transfered to a replacement group.<p>
     *
     * @param dbc the current request context
     * @param group the id of the group to be deleted
     * @param replacementId the id of the group to be transfered, can be <code>null</code>
     *
     * @throws CmsException if operation was not successful
     * @throws CmsDataAccessException if group to be deleted contains user
     */
    public void deleteGroup(CmsDbContext dbc, CmsGroup group, CmsUUID replacementId)
    throws CmsDataAccessException, CmsException {

        CmsGroup replacementGroup = null;
        if (replacementId != null) {
            replacementGroup = readGroup(dbc, replacementId);
        }
        // get all child groups of the group
        List<CmsGroup> children = getChildren(dbc, group, false);
        // get all users in this group
        List<CmsUser> users = getUsersOfGroup(dbc, group.getName(), true, true, group.isRole());
        // get online project
        CmsProject onlineProject = readProject(dbc, CmsProject.ONLINE_PROJECT_ID);
        if (replacementGroup == null) {
            // remove users
            Iterator<CmsUser> itUsers = users.iterator();
            while (itUsers.hasNext()) {
                CmsUser user = itUsers.next();
                if (userInGroup(dbc, user.getName(), group.getName(), group.isRole())) {
                    removeUserFromGroup(dbc, user.getName(), group.getName(), group.isRole());
                }
            }
            // transfer children to grandfather if possible
            CmsUUID parentId = group.getParentId();
            if (parentId == null) {
                parentId = CmsUUID.getNullUUID();
            }
            Iterator<CmsGroup> itChildren = children.iterator();
            while (itChildren.hasNext()) {
                CmsGroup child = itChildren.next();
                child.setParentId(parentId);
                writeGroup(dbc, child);
            }
        } else {
            // move children
            Iterator<CmsGroup> itChildren = children.iterator();
            while (itChildren.hasNext()) {
                CmsGroup child = itChildren.next();
                child.setParentId(replacementId);
                writeGroup(dbc, child);
            }
            // move users
            Iterator<CmsUser> itUsers = users.iterator();
            while (itUsers.hasNext()) {
                CmsUser user = itUsers.next();
                addUserToGroup(dbc, user.getName(), replacementGroup.getName(), group.isRole());
                removeUserFromGroup(dbc, user.getName(), group.getName(), group.isRole());
            }
            // transfer for offline
            transferPrincipalResources(dbc, dbc.currentProject(), group.getId(), replacementId, true);
            // transfer for online
            transferPrincipalResources(dbc, onlineProject, group.getId(), replacementId, true);
        }
        // remove the group
        getUserDriver(
            dbc).removeAccessControlEntriesForPrincipal(dbc, dbc.currentProject(), onlineProject, group.getId());
        getUserDriver(dbc).deleteGroup(dbc, group.getName());
        // backup the group
        getHistoryDriver(dbc).writePrincipal(dbc, group);
        if (OpenCms.getSubscriptionManager().isEnabled()) {
            // delete all subscribed resources for group
            unsubscribeAllResourcesFor(dbc, OpenCms.getSubscriptionManager().getPoolName(), group);
        }

        // clear the relevant caches
        m_monitor.uncacheGroup(group);
        m_monitor.flushCache(
            CmsMemoryMonitor.CacheType.USERGROUPS,
            CmsMemoryMonitor.CacheType.USER_LIST,
            CmsMemoryMonitor.CacheType.ACL);

        if (!dbc.getProjectId().isNullUUID()) {
            // group modified event is not needed
            return;
        }
        // fire group modified event
        Map<String, Object> eventData = new HashMap<String, Object>();
        eventData.put(I_CmsEventListener.KEY_GROUP_ID, group.getId().toString());
        eventData.put(I_CmsEventListener.KEY_GROUP_NAME, group.getName());
        eventData.put(I_CmsEventListener.KEY_USER_ACTION, I_CmsEventListener.VALUE_GROUP_MODIFIED_ACTION_DELETE);
        OpenCms.fireCmsEvent(new CmsEvent(I_CmsEventListener.EVENT_GROUP_MODIFIED, eventData));
    }

    /**
     * Deletes the versions from the history tables, keeping the given number of versions per resource.<p>
     *
     * if the <code>cleanUp</code> option is set, additionally versions of deleted resources will be removed.<p>
     *
     * @param dbc the current database context
     * @param versionsToKeep number of versions to keep, is ignored if negative
     * @param versionsDeleted number of versions to keep for deleted resources, is ignored if negative
     * @param timeDeleted deleted resources older than this will also be deleted, is ignored if negative
     * @param report the report for output logging
     *
     * @throws CmsException if operation was not successful
     */
    public void deleteHistoricalVersions(
        CmsDbContext dbc,
        int versionsToKeep,
        int versionsDeleted,
        long timeDeleted,
        I_CmsReport report)
    throws CmsException {

        report.println(Messages.get().container(Messages.RPT_START_DELETE_VERSIONS_0), I_CmsReport.FORMAT_HEADLINE);
        if (versionsToKeep >= 0) {
            report.println(
                Messages.get().container(Messages.RPT_START_DELETE_ACT_VERSIONS_1, new Integer(versionsToKeep)),
                I_CmsReport.FORMAT_HEADLINE);

            List<I_CmsHistoryResource> resources = getHistoryDriver(dbc).getAllNotDeletedEntries(dbc);
            if (resources.isEmpty()) {
                report.println(Messages.get().container(Messages.RPT_DELETE_NOTHING_0), I_CmsReport.FORMAT_OK);
            }
            int n = resources.size();
            int m = 1;
            Iterator<I_CmsHistoryResource> itResources = resources.iterator();
            while (itResources.hasNext()) {
                I_CmsHistoryResource histResource = itResources.next();

                report.print(
                    org.opencms.report.Messages.get().container(
                        org.opencms.report.Messages.RPT_SUCCESSION_2,
                        String.valueOf(m),
                        String.valueOf(n)),
                    I_CmsReport.FORMAT_NOTE);
                report.print(
                    org.opencms.report.Messages.get().container(
                        org.opencms.report.Messages.RPT_ARGUMENT_1,
                        dbc.removeSiteRoot(histResource.getRootPath())));
                report.print(org.opencms.report.Messages.get().container(org.opencms.report.Messages.RPT_DOTS_0));

                try {
                    int deleted = getHistoryDriver(dbc).deleteEntries(dbc, histResource, versionsToKeep, -1);

                    report.print(
                        Messages.get().container(Messages.RPT_VERSION_DELETING_1, new Integer(deleted)),
                        I_CmsReport.FORMAT_NOTE);
                    report.print(org.opencms.report.Messages.get().container(org.opencms.report.Messages.RPT_DOTS_0));
                    report.println(
                        org.opencms.report.Messages.get().container(org.opencms.report.Messages.RPT_OK_0),
                        I_CmsReport.FORMAT_OK);
                } catch (CmsDataAccessException e) {
                    report.println(
                        org.opencms.report.Messages.get().container(org.opencms.report.Messages.RPT_ERROR_0),
                        I_CmsReport.FORMAT_ERROR);

                    if (LOG.isDebugEnabled()) {
                        LOG.debug(e.getLocalizedMessage(), e);
                    }
                }

                m++;
            }

            report.println(
                Messages.get().container(Messages.RPT_END_DELETE_ACT_VERSIONS_0),
                I_CmsReport.FORMAT_HEADLINE);
        }
        if ((versionsDeleted >= 0) || (timeDeleted >= 0)) {
            if (timeDeleted >= 0) {
                report.println(
                    Messages.get().container(
                        Messages.RPT_START_DELETE_DEL_VERSIONS_2,
                        new Integer(versionsDeleted),
                        new Date(timeDeleted)),
                    I_CmsReport.FORMAT_HEADLINE);
            } else {
                report.println(
                    Messages.get().container(Messages.RPT_START_DELETE_DEL_VERSIONS_1, new Integer(versionsDeleted)),
                    I_CmsReport.FORMAT_HEADLINE);
            }
            List<I_CmsHistoryResource> resources = getHistoryDriver(dbc).getAllDeletedEntries(dbc);
            if (resources.isEmpty()) {
                report.println(Messages.get().container(Messages.RPT_DELETE_NOTHING_0), I_CmsReport.FORMAT_OK);
            }
            int n = resources.size();
            int m = 1;
            Iterator<I_CmsHistoryResource> itResources = resources.iterator();
            while (itResources.hasNext()) {
                I_CmsHistoryResource histResource = itResources.next();

                report.print(
                    org.opencms.report.Messages.get().container(
                        org.opencms.report.Messages.RPT_SUCCESSION_2,
                        String.valueOf(m),
                        String.valueOf(n)),
                    I_CmsReport.FORMAT_NOTE);
                report.print(
                    org.opencms.report.Messages.get().container(
                        org.opencms.report.Messages.RPT_ARGUMENT_1,
                        dbc.removeSiteRoot(histResource.getRootPath())));
                report.print(org.opencms.report.Messages.get().container(org.opencms.report.Messages.RPT_DOTS_0));

                try {
                    int deleted = getHistoryDriver(dbc).deleteEntries(dbc, histResource, versionsDeleted, timeDeleted);

                    report.print(
                        Messages.get().container(Messages.RPT_VERSION_DELETING_1, new Integer(deleted)),
                        I_CmsReport.FORMAT_NOTE);
                    report.print(org.opencms.report.Messages.get().container(org.opencms.report.Messages.RPT_DOTS_0));
                    report.println(
                        org.opencms.report.Messages.get().container(org.opencms.report.Messages.RPT_OK_0),
                        I_CmsReport.FORMAT_OK);
                } catch (CmsDataAccessException e) {
                    report.println(
                        org.opencms.report.Messages.get().container(org.opencms.report.Messages.RPT_ERROR_0),
                        I_CmsReport.FORMAT_ERROR);

                    if (LOG.isDebugEnabled()) {
                        LOG.debug(e.getLocalizedMessage(), e);
                    }
                }

                m++;
            }
            report.println(
                Messages.get().container(Messages.RPT_END_DELETE_DEL_VERSIONS_0),
                I_CmsReport.FORMAT_HEADLINE);
        }
        report.println(Messages.get().container(Messages.RPT_END_DELETE_VERSIONS_0), I_CmsReport.FORMAT_HEADLINE);
    }

    /**
     * Deletes all log entries matching the given filter.<p>
     *
     * @param dbc the current db context
     * @param filter the filter to use for deletion
     *
     * @throws CmsException if something goes wrong
     *
     * @see CmsSecurityManager#deleteLogEntries(CmsRequestContext, CmsLogFilter)
     */
    public void deleteLogEntries(CmsDbContext dbc, CmsLogFilter filter) throws CmsException {

        updateLog(dbc);
        m_projectDriver.deleteLog(dbc, filter);
    }

    /**
     * Deletes an organizational unit.<p>
     *
     * Only organizational units that contain no suborganizational unit can be deleted.<p>
     *
     * The organizational unit can not be delete if it is used in the request context,
     * or if the current user belongs to it.<p>
     *
     * All users and groups in the given organizational unit will be deleted.<p>
     *
     * @param dbc the current db context
     * @param organizationalUnit the organizational unit to delete
     *
     * @throws CmsException if operation was not successful
     *
     * @see org.opencms.security.CmsOrgUnitManager#deleteOrganizationalUnit(CmsObject, String)
     */
    public void deleteOrganizationalUnit(CmsDbContext dbc, CmsOrganizationalUnit organizationalUnit)
    throws CmsException {

        // check organizational unit in context
        if (dbc.getRequestContext().getOuFqn().equals(organizationalUnit.getName())) {
            throw new CmsDbConsistencyException(
                Messages.get().container(Messages.ERR_ORGUNIT_DELETE_IN_CONTEXT_1, organizationalUnit.getName()));
        }
        // check organizational unit for user
        if (dbc.currentUser().getOuFqn().equals(organizationalUnit.getName())) {
            throw new CmsDbConsistencyException(
                Messages.get().container(Messages.ERR_ORGUNIT_DELETE_CURRENT_USER_1, organizationalUnit.getName()));
        }
        // check sub organizational units
        if (!getOrganizationalUnits(dbc, organizationalUnit, true).isEmpty()) {
            throw new CmsDbConsistencyException(
                Messages.get().container(Messages.ERR_ORGUNIT_DELETE_SUB_ORGUNITS_1, organizationalUnit.getName()));
        }
        // check groups
        List<CmsGroup> groups = getGroups(dbc, organizationalUnit, true, false);
        Iterator<CmsGroup> itGroups = groups.iterator();
        while (itGroups.hasNext()) {
            CmsGroup group = itGroups.next();
            if (!OpenCms.getDefaultUsers().isDefaultGroup(group.getName())) {
                throw new CmsDbConsistencyException(
                    Messages.get().container(Messages.ERR_ORGUNIT_DELETE_GROUPS_1, organizationalUnit.getName()));
            }
        }
        // check users
        if (!getUsers(dbc, organizationalUnit, true).isEmpty()) {
            throw new CmsDbConsistencyException(
                Messages.get().container(Messages.ERR_ORGUNIT_DELETE_USERS_1, organizationalUnit.getName()));
        }

        // delete default groups if needed
        itGroups = groups.iterator();
        while (itGroups.hasNext()) {
            CmsGroup group = itGroups.next();
            deleteGroup(dbc, group, null);
        }

        // delete projects
        Iterator<CmsProject> itProjects = getProjectDriver(dbc).readProjects(
            dbc,
            organizationalUnit.getName()).iterator();
        while (itProjects.hasNext()) {
            CmsProject project = itProjects.next();
            deleteProject(dbc, project, false);
        }

        // delete roles
        Iterator<CmsGroup> itRoles = getGroups(dbc, organizationalUnit, true, true).iterator();
        while (itRoles.hasNext()) {
            CmsGroup role = itRoles.next();
            deleteGroup(dbc, role, null);
        }

        // create a publish list for the 'virtual' publish event
        CmsResource resource = readResource(dbc, organizationalUnit.getId(), CmsResourceFilter.DEFAULT);
        CmsPublishList pl = new CmsPublishList(resource, false);
        pl.add(resource, false);

        // remove the organizational unit itself
        getUserDriver(dbc).deleteOrganizationalUnit(dbc, organizationalUnit);

        // write the publish history entry
        getProjectDriver(dbc).writePublishHistory(
            dbc,
            pl.getPublishHistoryId(),
            new CmsPublishedResource(resource, -1, CmsResourceState.STATE_DELETED));

        // flush relevant caches
        m_monitor.clearPrincipalsCache();
        m_monitor.flushCache(CmsMemoryMonitor.CacheType.PROPERTY, CmsMemoryMonitor.CacheType.PROPERTY_LIST);

        // fire the 'virtual' publish event
        Map<String, Object> eventData = new HashMap<String, Object>();
        eventData.put(I_CmsEventListener.KEY_PUBLISHID, pl.getPublishHistoryId().toString());
        eventData.put(I_CmsEventListener.KEY_PROJECTID, dbc.currentProject().getUuid());
        eventData.put(I_CmsEventListener.KEY_DBCONTEXT, dbc);
        CmsEvent afterPublishEvent = new CmsEvent(I_CmsEventListener.EVENT_PUBLISH_PROJECT, eventData);
        OpenCms.fireCmsEvent(afterPublishEvent);

        m_lockManager.removeDeletedResource(dbc, resource.getRootPath());

        if (!dbc.getProjectId().isNullUUID()) {
            // OU modified event is not needed
            return;
        }
        // fire OU modified event
        Map<String, Object> event2Data = new HashMap<String, Object>();
        event2Data.put(I_CmsEventListener.KEY_OU_NAME, organizationalUnit.getName());
        event2Data.put(I_CmsEventListener.KEY_USER_ACTION, I_CmsEventListener.VALUE_OU_MODIFIED_ACTION_DELETE);
        OpenCms.fireCmsEvent(new CmsEvent(I_CmsEventListener.EVENT_OU_MODIFIED, event2Data));

    }

    /**
     * Deletes a project.<p>
     *
     * Only the admin or the owner of the project can do this.
     *
     * @param dbc the current database context
     * @param deleteProject the project to be deleted
     *
     * @throws CmsException if something goes wrong
     */
    public void deleteProject(CmsDbContext dbc, CmsProject deleteProject) throws CmsException {

        deleteProject(dbc, deleteProject, true);
    }

    /**
     * Deletes a project.<p>
     *
     * Only the admin or the owner of the project can do this.
     *
     * @param dbc the current database context
     * @param deleteProject the project to be deleted
     * @param resetResources if true, the resources of the project to delete will be reset to their online state, or deleted if they have no online state
     *
     * @throws CmsException if something goes wrong
     */
    public void deleteProject(CmsDbContext dbc, CmsProject deleteProject, boolean resetResources) throws CmsException {

        CmsUUID projectId = deleteProject.getUuid();

        if (resetResources) {
            // changed/new/deleted files in the specified project
            List<CmsResource> modifiedFiles = readChangedResourcesInsideProject(dbc, projectId, RCPRM_FILES_ONLY_MODE);
            // changed/new/deleted folders in the specified project
            List<CmsResource> modifiedFolders = readChangedResourcesInsideProject(
                dbc,
                projectId,
                RCPRM_FOLDERS_ONLY_MODE);
            resetResourcesInProject(dbc, projectId, modifiedFiles, modifiedFolders);
        }

        // unlock all resources in the project
        m_lockManager.removeResourcesInProject(deleteProject.getUuid(), true);
        m_monitor.clearAccessControlListCache();
        m_monitor.clearResourceCache();

        // set project to online project if current project is the one which will be deleted
        if (projectId.equals(dbc.currentProject().getUuid())) {
            dbc.getRequestContext().setCurrentProject(readProject(dbc, CmsProject.ONLINE_PROJECT_ID));
        }

        // delete the project itself
        getProjectDriver(dbc).deleteProject(dbc, deleteProject);
        m_monitor.uncacheProject(deleteProject);

        // fire the corresponding event
        OpenCms.fireCmsEvent(
            new CmsEvent(
                I_CmsEventListener.EVENT_PROJECT_MODIFIED,
                Collections.<String, Object> singletonMap("project", deleteProject)));

    }

    /**
     * Deletes a property definition.<p>
     *
     * @param dbc the current database context
     * @param name the name of the property definition to delete
     *
     * @throws CmsException if something goes wrong
     */
    public void deletePropertyDefinition(CmsDbContext dbc, String name) throws CmsException {

        CmsPropertyDefinition propertyDefinition = null;

        try {
            // first read and then delete the metadefinition.
            propertyDefinition = readPropertyDefinition(dbc, name);
            getVfsDriver(dbc).deletePropertyDefinition(dbc, propertyDefinition);
            getHistoryDriver(dbc).deletePropertyDefinition(dbc, propertyDefinition);
        } finally {

            // fire an event that a property of a resource has been deleted
            OpenCms.fireCmsEvent(
                new CmsEvent(
                    I_CmsEventListener.EVENT_PROPERTY_DEFINITION_MODIFIED,
                    Collections.<String, Object> singletonMap("propertyDefinition", propertyDefinition)));
        }
    }

    /**
     * Deletes a publish job identified by its history id.<p>
     *
     * @param dbc the current database context
     * @param publishHistoryId the history id identifying the publish job
     *
     * @throws CmsException if something goes wrong
     */
    public void deletePublishJob(CmsDbContext dbc, CmsUUID publishHistoryId) throws CmsException {

        getProjectDriver(dbc).deletePublishJob(dbc, publishHistoryId);
    }

    /**
     * Deletes the publish list assigned to a publish job.<p>
     *
     * @param dbc the current database context
     * @param publishHistoryId the history id identifying the publish job
     * @throws CmsException if something goes wrong
     */
    public void deletePublishList(CmsDbContext dbc, CmsUUID publishHistoryId) throws CmsException {

        getProjectDriver(dbc).deletePublishList(dbc, publishHistoryId);
    }

    /**
     * Deletes all relations for the given resource matching the given filter.<p>
     *
     * @param dbc the current db context
     * @param resource the resource to delete the relations for
     * @param filter the filter to use for deletion
     *
     * @throws CmsException if something goes wrong
     *
     * @see CmsSecurityManager#deleteRelationsForResource(CmsRequestContext, CmsResource, CmsRelationFilter)
     */
    public void deleteRelationsForResource(CmsDbContext dbc, CmsResource resource, CmsRelationFilter filter)
    throws CmsException {

        if (filter.includesDefinedInContent()) {
            throw new CmsIllegalArgumentException(
                Messages.get().container(
                    Messages.ERR_DELETE_RELATION_IN_CONTENT_2,
                    dbc.removeSiteRoot(resource.getRootPath()),
                    filter.getTypes()));
        }
        getVfsDriver(dbc).deleteRelations(dbc, dbc.currentProject().getUuid(), resource, filter);
        setDateLastModified(dbc, resource, System.currentTimeMillis());
        log(
            dbc,
            new CmsLogEntry(
                dbc,
                resource.getStructureId(),
                CmsLogEntryType.RESOURCE_REMOVE_RELATION,
                new String[] {resource.getRootPath(), filter.toString()}),
            false);
    }

    /**
     * Deletes a resource.<p>
     *
     * The <code>siblingMode</code> parameter controls how to handle siblings
     * during the delete operation.
     * Possible values for this parameter are:
     * <ul>
     * <li><code>{@link CmsResource#DELETE_REMOVE_SIBLINGS}</code></li>
     * <li><code>{@link CmsResource#DELETE_PRESERVE_SIBLINGS}</code></li>
     * </ul><p>
     *
     * @param dbc the current database context
     * @param resource the name of the resource to delete (full path)
     * @param siblingMode indicates how to handle siblings of the deleted resource
     *
     * @throws CmsException if something goes wrong
     *
     * @see CmsObject#deleteResource(String, CmsResource.CmsResourceDeleteMode)
     * @see I_CmsResourceType#deleteResource(CmsObject, CmsSecurityManager, CmsResource, CmsResource.CmsResourceDeleteMode)
     */
    public void deleteResource(CmsDbContext dbc, CmsResource resource, CmsResource.CmsResourceDeleteMode siblingMode)
    throws CmsException {

        // upgrade a potential inherited, non-shared lock into a common lock
        CmsLock currentLock = getLock(dbc, resource);
        if (currentLock.getEditionLock().isDirectlyInherited()) {
            // upgrade the lock status if required
            lockResource(dbc, resource, CmsLockType.EXCLUSIVE);
        }

        // check if siblings of the resource exist and must be deleted as well
        if (resource.isFolder()) {
            // folder can have no siblings
            siblingMode = CmsResource.DELETE_PRESERVE_SIBLINGS;
        }

        // if selected, add all siblings of this resource to the list of resources to be deleted
        boolean allSiblingsRemoved;
        List<CmsResource> resources;
        if (siblingMode == CmsResource.DELETE_REMOVE_SIBLINGS) {
            resources = new ArrayList<CmsResource>(readSiblings(dbc, resource, CmsResourceFilter.ALL));
            allSiblingsRemoved = true;

            // ensure that the resource requested to be deleted is the last resource that gets actually deleted
            // to keep the shared locks of the siblings while those get deleted.
            resources.remove(resource);
            resources.add(resource);
        } else {
            // only delete the resource, no siblings
            resources = Collections.singletonList(resource);
            allSiblingsRemoved = false;
        }

        int size = resources.size();
        // if we have only one resource no further check is required
        if (size > 1) {
            CmsMultiException me = new CmsMultiException();
            // ensure that each sibling is unlocked or locked by the current user
            for (int i = 0; i < size; i++) {
                CmsResource currentResource = resources.get(i);
                currentLock = getLock(dbc, currentResource);
                if (!currentLock.getEditionLock().isUnlocked() && !currentLock.isOwnedBy(dbc.currentUser())) {
                    // the resource is locked by a user different from the current user
                    CmsRequestContext context = dbc.getRequestContext();
                    me.addException(
                        new CmsLockException(
                            org.opencms.lock.Messages.get().container(
                                org.opencms.lock.Messages.ERR_SIBLING_LOCKED_2,
                                context.getSitePath(currentResource),
                                context.getSitePath(resource))));
                }
            }
            if (!me.getExceptions().isEmpty()) {
                throw me;
            }
        }

        boolean removeAce = true;

        if (resource.isFolder()) {
            // check if the folder has any resources in it
            Iterator<CmsResource> childResources = getVfsDriver(
                dbc).readChildResources(dbc, dbc.currentProject(), resource, true, true).iterator();

            CmsUUID projectId = CmsProject.ONLINE_PROJECT_ID;
            if (dbc.currentProject().isOnlineProject()) {
                projectId = CmsUUID.getOpenCmsUUID(); // HACK: to get an offline project id
            }

            // collect the names of the resources inside the folder, excluding the moved resources
            StringBuffer errorResNames = new StringBuffer(128);
            while (childResources.hasNext()) {
                CmsResource errorRes = childResources.next();
                if (errorRes.getState().isDeleted()) {
                    continue;
                }
                // if deleting offline, or not moved, or just renamed inside the deleted folder
                // so, it may remain some orphan online entries for moved resources
                // which will be fixed during the publishing of the moved resources
                boolean error = !dbc.currentProject().isOnlineProject();
                if (!error) {
                    try {
                        String originalPath = getVfsDriver(
                            dbc).readResource(dbc, projectId, errorRes.getRootPath(), true).getRootPath();
                        error = originalPath.equals(errorRes.getRootPath())
                            || originalPath.startsWith(resource.getRootPath());
                    } catch (CmsVfsResourceNotFoundException e) {
                        // ignore
                    }
                }
                if (error) {
                    if (errorResNames.length() != 0) {
                        errorResNames.append(", ");
                    }
                    errorResNames.append("[" + dbc.removeSiteRoot(errorRes.getRootPath()) + "]");
                }
            }

            // the current implementation only deletes empty folders
            if (CmsStringUtil.isNotEmptyOrWhitespaceOnly(errorResNames.toString())) {
                throw new CmsVfsException(
                    org.opencms.db.generic.Messages.get().container(
                        org.opencms.db.generic.Messages.ERR_DELETE_NONEMTY_FOLDER_2,
                        dbc.removeSiteRoot(resource.getRootPath()),
                        errorResNames.toString()));
            }
        }

        // delete all collected resources
        for (int i = 0; i < size; i++) {
            CmsResource currentResource = resources.get(i);

            // try to delete/remove the resource only if the user has write access to the resource
            // check permissions only for the sibling, the resource it self was already checked or
            // is to be removed without write permissions, ie. while deleting a folder
            if (!currentResource.equals(resource)
                && (I_CmsPermissionHandler.PERM_ALLOWED != m_securityManager.hasPermissions(
                    dbc,
                    currentResource,
                    CmsPermissionSet.ACCESS_WRITE,
                    true,
                    CmsResourceFilter.ALL))) {

                // no write access to sibling - must keep ACE (see below)
                allSiblingsRemoved = false;
            } else {
                // write access to sibling granted
                boolean existsOnline = (getVfsDriver(dbc).validateStructureIdExists(
                    dbc,
                    CmsProject.ONLINE_PROJECT_ID,
                    currentResource.getStructureId()) || !(currentResource.getState().equals(CmsResource.STATE_NEW)));
                if (!existsOnline) {
                    // the resource does not exist online => remove the resource
                    // this means the resource is "new" (blue) in the offline project

                    // delete all properties of this resource
                    deleteAllProperties(dbc, currentResource.getRootPath());

                    if (currentResource.isFolder()) {
                        getVfsDriver(dbc).removeFolder(dbc, dbc.currentProject(), currentResource);
                    } else {
                        // check labels
                        if (currentResource.isLabeled() && !labelResource(dbc, currentResource, null, 2)) {
                            // update the resource flags to "un label" the other siblings
                            int flags = currentResource.getFlags();
                            flags &= ~CmsResource.FLAG_LABELED;
                            currentResource.setFlags(flags);
                        }
                        getVfsDriver(dbc).removeFile(dbc, dbc.currentProject().getUuid(), currentResource);
                    }

                    // ensure an exclusive lock is removed in the lock manager for a deleted new resource,
                    // otherwise it would "stick" in the lock manager, preventing other users from creating
                    // a file with the same name (issue with temp files in editor)
                    m_lockManager.removeDeletedResource(dbc, currentResource.getRootPath());
                    // delete relations
                    getVfsDriver(dbc).deleteRelations(
                        dbc,
                        dbc.currentProject().getUuid(),
                        currentResource,
                        CmsRelationFilter.TARGETS);
                    getVfsDriver(dbc).deleteUrlNameMappingEntries(
                        dbc,
                        false,
                        CmsUrlNameMappingFilter.ALL.filterStructureId(currentResource.getStructureId()));
                    getVfsDriver(dbc).deleteAliases(
                        dbc,
                        dbc.currentProject(),
                        new CmsAliasFilter(null, null, currentResource.getStructureId()));
                    log(
                        dbc,
                        new CmsLogEntry(
                            dbc,
                            currentResource.getStructureId(),
                            CmsLogEntryType.RESOURCE_NEW_DELETED,
                            new String[] {currentResource.getRootPath()}),
                        true);
                } else {
                    // the resource exists online => mark the resource as deleted
                    // structure record is removed during next publish
                    // if one (or more) siblings are not removed, the ACE can not be removed
                    removeAce = false;
                    // set resource state to deleted
                    currentResource.setState(CmsResource.STATE_DELETED);
                    getVfsDriver(
                        dbc).writeResourceState(dbc, dbc.currentProject(), currentResource, UPDATE_STRUCTURE, false);

                    // update the project ID
                    getVfsDriver(dbc).writeLastModifiedProjectId(
                        dbc,
                        dbc.currentProject(),
                        dbc.currentProject().getUuid(),
                        currentResource);
                    // log it

                    log(
                        dbc,
                        new CmsLogEntry(
                            dbc,
                            currentResource.getStructureId(),
                            CmsLogEntryType.RESOURCE_DELETED,
                            new String[] {currentResource.getRootPath()}),
                        true);
                }
            }
        }

        if ((resource.getSiblingCount() <= 1) || allSiblingsRemoved) {
            if (removeAce) {
                // remove the access control entries
                getUserDriver(dbc).removeAccessControlEntries(dbc, dbc.currentProject(), resource.getResourceId());
            }
        }

        // flush all caches
        m_monitor.clearAccessControlListCache();
        m_monitor.flushCache(
            CmsMemoryMonitor.CacheType.PROPERTY,
            CmsMemoryMonitor.CacheType.PROPERTY_LIST,
            CmsMemoryMonitor.CacheType.PROJECT_RESOURCES);

        Map<String, Object> eventData = new HashMap<String, Object>();
        eventData.put(I_CmsEventListener.KEY_RESOURCES, resources);
        eventData.put(I_CmsEventListener.KEY_DBCONTEXT, dbc);
        OpenCms.fireCmsEvent(new CmsEvent(I_CmsEventListener.EVENT_RESOURCE_DELETED, eventData));
    }

    /**
     * Deletes an entry in the published resource table.<p>
     *
     * @param dbc the current database context
     * @param resourceName The name of the resource to be deleted in the static export
     * @param linkType the type of resource deleted (0= non-parameter, 1=parameter)
     * @param linkParameter the parameters of the resource
     *
     * @throws CmsException if something goes wrong
     */
    public void deleteStaticExportPublishedResource(
        CmsDbContext dbc,
        String resourceName,
        int linkType,
        String linkParameter)
    throws CmsException {

        getProjectDriver(dbc).deleteStaticExportPublishedResource(dbc, resourceName, linkType, linkParameter);
    }

    /**
     * Deletes a user, where all permissions and resources attributes of the user
     * were transfered to a replacement user, if given.<p>
     *
     * Only users, which are in the group "administrators" are granted.<p>
     *
     * @param dbc the current database context
     * @param project the current project
     * @param username the name of the user to be deleted
     * @param replacementUsername the name of the user to be transfered, can be <code>null</code>
     *
     * @throws CmsException if operation was not successful
     */
    public void deleteUser(CmsDbContext dbc, CmsProject project, String username, String replacementUsername)
    throws CmsException {

        // Test if the users exists
        CmsUser user = readUser(dbc, username);
        CmsUser replacementUser = null;
        if (replacementUsername != null) {
            replacementUser = readUser(dbc, replacementUsername);
        }

        CmsProject onlineProject = readProject(dbc, CmsProject.ONLINE_PROJECT_ID);
        boolean withACEs = true;
        if (replacementUser == null) {
            withACEs = false;
            replacementUser = readUser(dbc, OpenCms.getDefaultUsers().getUserDeletedResource());
        }

        boolean isVfsManager = m_securityManager.hasRole(dbc, replacementUser, CmsRole.VFS_MANAGER);

        // iterate groups and roles
        for (int i = 0; i < 2; i++) {
            boolean readRoles = i != 0;
            Iterator<CmsGroup> itGroups = getGroupsOfUser(
                dbc,
                username,
                "",
                true,
                readRoles,
                true,
                dbc.getRequestContext().getRemoteAddress()).iterator();
            while (itGroups.hasNext()) {
                CmsGroup group = itGroups.next();
                if (!isVfsManager) {
                    // add replacement user to user groups
                    if (!userInGroup(dbc, replacementUser.getName(), group.getName(), readRoles)) {
                        addUserToGroup(dbc, replacementUser.getName(), group.getName(), readRoles);
                    }
                }
                // remove user from groups
                if (userInGroup(dbc, username, group.getName(), readRoles)) {
                    // we need this additional check because removing a user from a group
                    // may also automatically remove him from other groups if the group was
                    // associated with a role.
                    removeUserFromGroup(dbc, username, group.getName(), readRoles);
                }
            }
        }
        // remove all locks set for the deleted user
        m_lockManager.removeLocks(user.getId());
        // offline
        if (dbc.getProjectId().isNullUUID()) {
            // offline project available
            transferPrincipalResources(dbc, project, user.getId(), replacementUser.getId(), withACEs);
        }
        // online
        transferPrincipalResources(dbc, onlineProject, user.getId(), replacementUser.getId(), withACEs);
        getUserDriver(dbc).removeAccessControlEntriesForPrincipal(dbc, project, onlineProject, user.getId());
        getHistoryDriver(dbc).writePrincipal(dbc, user);
        getUserDriver(dbc).deleteUser(dbc, username);
        // delete user from cache
        m_monitor.clearUserCache(user);

        if (!dbc.getProjectId().isNullUUID()) {
            // user modified event is not needed
            return;
        }
        // fire user modified event
        Map<String, Object> eventData = new HashMap<String, Object>();
        eventData.put(I_CmsEventListener.KEY_USER_ID, user.getId().toString());
        eventData.put(I_CmsEventListener.KEY_USER_NAME, user.getName());
        eventData.put(I_CmsEventListener.KEY_USER_ACTION, I_CmsEventListener.VALUE_USER_MODIFIED_ACTION_DELETE_USER);
        OpenCms.fireCmsEvent(new CmsEvent(I_CmsEventListener.EVENT_USER_MODIFIED, eventData));
    }

    /**
     * Destroys this driver manager and releases all allocated resources.<p>
     */
    public void destroy() {

        try {
            if (m_projectDriver != null) {
                try {
                    m_projectDriver.destroy();
                } catch (Throwable t) {
                    LOG.error(Messages.get().getBundle().key(Messages.ERR_CLOSE_PROJECT_DRIVER_0), t);
                }
                m_projectDriver = null;
            }
            if (m_userDriver != null) {
                try {
                    m_userDriver.destroy();
                } catch (Throwable t) {
                    LOG.error(Messages.get().getBundle().key(Messages.ERR_CLOSE_USER_DRIVER_0), t);
                }
                m_userDriver = null;
            }
            if (m_vfsDriver != null) {
                try {
                    m_vfsDriver.destroy();
                } catch (Throwable t) {
                    LOG.error(Messages.get().getBundle().key(Messages.ERR_CLOSE_VFS_DRIVER_0), t);
                }
                m_vfsDriver = null;
            }
            if (m_historyDriver != null) {
                try {
                    m_historyDriver.destroy();
                } catch (Throwable t) {
                    LOG.error(Messages.get().getBundle().key(Messages.ERR_CLOSE_HISTORY_DRIVER_0), t);
                }
                m_historyDriver = null;
            }

            if (m_pools != null) {
                for (CmsDbPoolV11 pool : m_pools.values()) {
                    try {
                        pool.close();
                        if (CmsLog.INIT.isDebugEnabled()) {
                            CmsLog.INIT.debug(Messages.get().getBundle().key(Messages.INIT_CLOSE_CONN_POOL_1, pool));
                        }

                    } catch (Throwable t) {
                        LOG.error(Messages.get().getBundle().key(Messages.LOG_CLOSE_CONN_POOL_ERROR_1, pool), t);
                    }
                }
                m_pools.clear();
            }

            m_monitor.clearCache();

            m_lockManager = null;
            m_htmlLinkValidator = null;
        } catch (Throwable t) {
            // ignore
        }
        if (CmsLog.INIT.isInfoEnabled()) {
            CmsLog.INIT.info(
                Messages.get().getBundle().key(Messages.INIT_DRIVER_MANAGER_DESTROY_1, getClass().getName()));
        }
    }

    /**
     * Tests if a resource with the given resourceId does already exist in the Database.<p>
     *
     * @param dbc the current database context
     * @param resourceId the resource id to test for
     * @return true if a resource with the given id was found, false otherweise
     * @throws CmsException if something goes wrong
     */
    public boolean existsResourceId(CmsDbContext dbc, CmsUUID resourceId) throws CmsException {

        return getVfsDriver(dbc).validateResourceIdExists(dbc, dbc.currentProject().getUuid(), resourceId);
    }

    /**
     * Fills the given publish list with the the VFS resources that actually get published.<p>
     *
     * Please refer to the source code of this method for the rules on how to decide whether a
     * new/changed/deleted <code>{@link CmsResource}</code> object can be published or not.<p>
     *
     * @param dbc the current database context
     * @param publishList must be initialized with basic publish information (Project or direct publish operation),
     *                    the given publish list will be filled with all new/changed/deleted files from the current
     *                    (offline) project that will be actually published
     *
     * @throws CmsException if something goes wrong
     *
     * @see org.opencms.db.CmsPublishList
     */
    public void fillPublishList(CmsDbContext dbc, CmsPublishList publishList) throws CmsException {

        if (!publishList.isDirectPublish()) {
            // when publishing a project
            // all modified resources with the last change done in the current project are candidates if unlocked
            List<CmsResource> folderList = getVfsDriver(dbc).readResourceTree(
                dbc,
                dbc.currentProject().getUuid(),
                CmsDriverManager.READ_IGNORE_PARENT,
                CmsDriverManager.READ_IGNORE_TYPE,
                CmsResource.STATE_UNCHANGED,
                CmsDriverManager.READ_IGNORE_TIME,
                CmsDriverManager.READ_IGNORE_TIME,
                CmsDriverManager.READ_IGNORE_TIME,
                CmsDriverManager.READ_IGNORE_TIME,
                CmsDriverManager.READ_IGNORE_TIME,
                CmsDriverManager.READ_IGNORE_TIME,
                CmsDriverManager.READMODE_INCLUDE_TREE
                    | CmsDriverManager.READMODE_INCLUDE_PROJECT
                    | CmsDriverManager.READMODE_EXCLUDE_STATE
                    | CmsDriverManager.READMODE_ONLY_FOLDERS);

            List<CmsResource> fileList = getVfsDriver(dbc).readResourceTree(
                dbc,
                dbc.currentProject().getUuid(),
                CmsDriverManager.READ_IGNORE_PARENT,
                CmsDriverManager.READ_IGNORE_TYPE,
                CmsResource.STATE_UNCHANGED,
                CmsDriverManager.READ_IGNORE_TIME,
                CmsDriverManager.READ_IGNORE_TIME,
                CmsDriverManager.READ_IGNORE_TIME,
                CmsDriverManager.READ_IGNORE_TIME,
                CmsDriverManager.READ_IGNORE_TIME,
                CmsDriverManager.READ_IGNORE_TIME,
                CmsDriverManager.READMODE_INCLUDE_TREE
                    | CmsDriverManager.READMODE_INCLUDE_PROJECT
                    | CmsDriverManager.READMODE_EXCLUDE_STATE
                    | CmsDriverManager.READMODE_ONLY_FILES);
            CmsRequestContext context = dbc.getRequestContext();
            if ((context != null)
                && (context.getAttribute(CmsDefaultWorkflowManager.ATTR_CHECK_PUBLISH_RESOURCE_LIMIT) != null)) {

                // check if total size and if it exceeds the resource limit and the request
                // context attribute is set, throw an exception.
                // we do it here since filterResources() can be very expensive on large resource lists

                int limit = OpenCms.getWorkflowManager().getResourceLimit();
                int total = fileList.size() + folderList.size();
                if (total > limit) {
                    throw new CmsTooManyPublishResourcesException(total);
                }
            }
            publishList.addAll(filterResources(dbc, null, folderList), true);
            publishList.addAll(filterResources(dbc, publishList, fileList), true);
        } else {
            // this is a direct publish
            Iterator<CmsResource> it = publishList.getDirectPublishResources().iterator();
            while (it.hasNext()) {
                // iterate all resources in the direct publish list
                CmsResource directPublishResource = it.next();
                if (directPublishResource.isFolder()) {
                    // when publishing a folder directly,
                    // the folder and all modified resources within the tree below this folder
                    // and with the last change done in the current project are candidates if lockable
                    CmsLock lock = getLock(dbc, directPublishResource);
                    if (!directPublishResource.getState().isUnchanged() && lock.isLockableBy(dbc.currentUser())) {

                        try {
                            m_securityManager.checkPermissions(
                                dbc,
                                directPublishResource,
                                CmsPermissionSet.ACCESS_DIRECT_PUBLISH,
                                false,
                                CmsResourceFilter.ALL);
                            publishList.add(directPublishResource, true);
                        } catch (CmsException e) {
                            // skip if not enough permissions
                        }
                    }
                    boolean shouldPublishDeletedSubResources = publishList.isUserPublishList()
                        && directPublishResource.getState().isDeleted();
                    if (publishList.isPublishSubResources() || shouldPublishDeletedSubResources) {
                        addSubResources(dbc, publishList, directPublishResource, resource -> true);
                    }
                } else if (directPublishResource.isFile() && !directPublishResource.getState().isUnchanged()) {

                    // when publishing a file directly this file is the only candidate
                    // if it is modified and lockable
                    CmsLock lock = getLock(dbc, directPublishResource);
                    if (lock.isLockableBy(dbc.currentUser())) {
                        // check permissions
                        try {
                            m_securityManager.checkPermissions(
                                dbc,
                                directPublishResource,
                                CmsPermissionSet.ACCESS_DIRECT_PUBLISH,
                                false,
                                CmsResourceFilter.ALL);
                            publishList.add(directPublishResource, true);
                        } catch (CmsException e) {
                            // skip if not enough permissions
                        }
                    }
                }
            }
        }

        // Step 2: if desired, extend the list of files to publish with related siblings
        if (publishList.isPublishSiblings()) {
            List<CmsResource> publishFiles = publishList.getFileList();
            int size = publishFiles.size();

            // Improved: first calculate closure of all siblings, then filter and add them
            Set<CmsResource> siblingsClosure = new HashSet<CmsResource>(publishFiles);
            for (int i = 0; i < size; i++) {
                CmsResource currentFile = publishFiles.get(i);
                if (currentFile.getSiblingCount() > 1) {
                    siblingsClosure.addAll(readSiblings(dbc, currentFile, CmsResourceFilter.ALL_MODIFIED));
                }
            }
            publishList.addAll(filterSiblings(dbc, publishList, siblingsClosure), true);
        }
        publishList.initialize();
    }

    /**
     * Returns the list of access control entries of a resource given its name.<p>
     *
     * @param dbc the current database context
     * @param resource the resource to read the access control entries for
     * @param getInherited true if the result should include all access control entries inherited by parent folders
     *
     * @return a list of <code>{@link CmsAccessControlEntry}</code> objects defining all permissions for the given resource
     *
     * @throws CmsException if something goes wrong
     */
    public List<CmsAccessControlEntry> getAccessControlEntries(
        CmsDbContext dbc,
        CmsResource resource,
        boolean getInherited)
    throws CmsException {

        // get the ACE of the resource itself
        I_CmsUserDriver userDriver = getUserDriver(dbc);
        I_CmsVfsDriver vfsDriver = getVfsDriver(dbc);
        List<CmsAccessControlEntry> ace = userDriver.readAccessControlEntries(
            dbc,
            dbc.currentProject(),
            resource.getResourceId(),
            false);

        // sort and check if we got the 'overwrite all' ace to stop looking up
        boolean overwriteAll = sortAceList(ace);

        // get the ACE of each parent folder
        // Note: for the immediate parent, get non-inherited access control entries too,
        // if the resource is not a folder
        String parentPath = CmsResource.getParentFolder(resource.getRootPath());
        int d = (resource.isFolder()) ? 1 : 0;

        while (!overwriteAll && getInherited && (parentPath != null)) {
            resource = vfsDriver.readFolder(dbc, dbc.currentProject().getUuid(), parentPath);
            List<CmsAccessControlEntry> entries = userDriver.readAccessControlEntries(
                dbc,
                dbc.currentProject(),
                resource.getResourceId(),
                d > 0);

            // sort and check if we got the 'overwrite all' ace to stop looking up
            overwriteAll = sortAceList(entries);

            for (CmsAccessControlEntry e : entries) {
                e.setFlags(CmsAccessControlEntry.ACCESS_FLAGS_INHERITED);
            }

            ace.addAll(entries);
            parentPath = CmsResource.getParentFolder(resource.getRootPath());
            d++;
        }

        return ace;
    }

    /**
     * Returns the full access control list of a given resource.<p>
     *
     * @param dbc the current database context
     * @param resource the resource
     *
     * @return the access control list of the resource
     *
     * @throws CmsException if something goes wrong
     */
    public CmsAccessControlList getAccessControlList(CmsDbContext dbc, CmsResource resource) throws CmsException {

        return getAccessControlList(dbc, resource, false);
    }

    /**
     * Returns the access control list of a given resource.<p>
     *
     * If <code>inheritedOnly</code> is set, only inherited access control entries
     * are returned.<p>
     *
     * Note: For file resources, *all* permissions set at the immediate parent folder are inherited,
     * not only these marked to inherit.
     *
     * @param dbc the current database context
     * @param resource the resource
     * @param inheritedOnly skip non-inherited entries if set
     *
     * @return the access control list of the resource
     *
     * @throws CmsException if something goes wrong
     */
    public CmsAccessControlList getAccessControlList(CmsDbContext dbc, CmsResource resource, boolean inheritedOnly)
    throws CmsException {

        return getAccessControlList(dbc, resource, inheritedOnly, resource.isFolder(), 0);
    }

    /**
     * Returns the number of active connections managed by a pool.<p>
     *
     * @param dbPoolUrl the url of a pool
     * @return the number of active connections
     * @throws CmsDbException if something goes wrong
     */
    public int getActiveConnections(String dbPoolUrl) throws CmsDbException {

        CmsDbPoolV11 pool = m_pools.get(dbPoolUrl);
        if (pool == null) {
            CmsMessageContainer message = Messages.get().container(Messages.ERR_UNKNOWN_POOL_URL_1, dbPoolUrl);
            throw new CmsDbException(message);
        }
        try {
            return pool.getActiveConnections();
        } catch (Exception exc) {
            CmsMessageContainer message = Messages.get().container(Messages.ERR_ACCESSING_POOL_1, dbPoolUrl);
            throw new CmsDbException(message, exc);
        }

    }

    /**
     * Reads all access control entries.<p>
     *
     * @param dbc the current database context
     * @return all access control entries for the current project (offline/online)
     *
     * @throws CmsException if something goes wrong
     */
    public List<CmsAccessControlEntry> getAllAccessControlEntries(CmsDbContext dbc) throws CmsException {

        I_CmsUserDriver userDriver = getUserDriver(dbc);
        List<CmsAccessControlEntry> ace = userDriver.readAccessControlEntries(
            dbc,
            dbc.currentProject(),
            CmsAccessControlEntry.PRINCIPAL_READALL_ID,
            false);
        return ace;
    }

    /**
     * Returns all projects which are owned by the current user or which are
     * accessible by the current user.<p>
     *
     * @param dbc the current database context
     * @param orgUnit the organizational unit to search project in
     * @param includeSubOus if to include sub organizational units
     *
     * @return a list of objects of type <code>{@link CmsProject}</code>
     *
     * @throws CmsException if something goes wrong
     */
    public List<CmsProject> getAllAccessibleProjects(
        CmsDbContext dbc,
        CmsOrganizationalUnit orgUnit,
        boolean includeSubOus)
    throws CmsException {

        Set<CmsProject> projects = new HashSet<CmsProject>();

        // get the ous where the user has the project manager role
        List<CmsOrganizationalUnit> ous = getOrgUnitsForRole(
            dbc,
            CmsRole.PROJECT_MANAGER.forOrgUnit(orgUnit.getName()),
            includeSubOus);

        // get the groups of the user if needed
        Set<CmsUUID> userGroupIds = new HashSet<CmsUUID>();
        Iterator<CmsGroup> itGroups = getGroupsOfUser(dbc, dbc.currentUser().getName(), false).iterator();
        while (itGroups.hasNext()) {
            CmsGroup group = itGroups.next();
            userGroupIds.add(group.getId());
        }

        // TODO: this could be optimize if this method would have an additional parameter 'includeSubOus'
        // get all projects that might come in question
        projects.addAll(getProjectDriver(dbc).readProjects(dbc, orgUnit.getName()));

        // filter hidden and not accessible projects
        Iterator<CmsProject> itProjects = projects.iterator();
        while (itProjects.hasNext()) {
            CmsProject project = itProjects.next();
            boolean accessible = true;
            // if hidden
            accessible = accessible && !project.isHidden();

            if (!includeSubOus) {
                // if not exact in the given ou
                accessible = accessible && project.getOuFqn().equals(orgUnit.getName());
            } else {
                // if not in the given ou
                accessible = accessible && project.getOuFqn().startsWith(orgUnit.getName());
            }

            if (!accessible) {
                itProjects.remove();
                continue;
            }

            accessible = false;
            // online project
            accessible = accessible || project.isOnlineProject();
            // if owner
            accessible = accessible || project.getOwnerId().equals(dbc.currentUser().getId());

            // project managers
            Iterator<CmsOrganizationalUnit> itOus = ous.iterator();
            while (!accessible && itOus.hasNext()) {
                CmsOrganizationalUnit ou = itOus.next();
                // for project managers check visibility
                accessible = accessible || project.getOuFqn().startsWith(ou.getName());
            }

            if (!accessible) {
                // if direct user or manager of project
                CmsUUID groupId = null;
                if (userGroupIds.contains(project.getGroupId())) {
                    groupId = project.getGroupId();
                } else if (userGroupIds.contains(project.getManagerGroupId())) {
                    groupId = project.getManagerGroupId();
                }
                if (groupId != null) {
                    String oufqn = readGroup(dbc, groupId).getOuFqn();
                    accessible = accessible || (oufqn.startsWith(dbc.getRequestContext().getOuFqn()));
                }
            }
            if (!accessible) {
                // remove not accessible project
                itProjects.remove();
            }
        }

        List<CmsProject> accessibleProjects = new ArrayList<CmsProject>(projects);
        // sort the list of projects based on the project name
        Collections.sort(accessibleProjects);
        // ensure the online project is in first place
        CmsProject onlineProject = readProject(dbc, CmsProject.ONLINE_PROJECT_ID);
        if (accessibleProjects.contains(onlineProject)) {
            accessibleProjects.remove(onlineProject);
        }
        accessibleProjects.add(0, onlineProject);

        return accessibleProjects;
    }

    /**
     * Returns a list with all projects from history.<p>
     *
     * @param dbc the current database context
     *
     * @return list of <code>{@link CmsHistoryProject}</code> objects
     *           with all projects from history.
     *
     * @throws CmsException if operation was not successful
     */
    public List<CmsHistoryProject> getAllHistoricalProjects(CmsDbContext dbc) throws CmsException {

        // user is allowed to access all existing projects for the ous he has the project_manager role
        Set<CmsOrganizationalUnit> manOus = new HashSet<CmsOrganizationalUnit>(
            getOrgUnitsForRole(dbc, CmsRole.PROJECT_MANAGER, true));

        List<CmsHistoryProject> projects = getHistoryDriver(dbc).readProjects(dbc);
        Iterator<CmsHistoryProject> itProjects = projects.iterator();
        while (itProjects.hasNext()) {
            CmsHistoryProject project = itProjects.next();
            if (project.isHidden()) {
                // project is hidden
                itProjects.remove();
                continue;
            }
            if (!project.getOuFqn().startsWith(dbc.currentUser().getOuFqn())) {
                // project is not visible from the users ou
                itProjects.remove();
                continue;
            }
            CmsOrganizationalUnit ou = readOrganizationalUnit(dbc, project.getOuFqn());
            if (manOus.contains(ou)) {
                // user is project manager for this project
                continue;
            } else if (project.getOwnerId().equals(dbc.currentUser().getId())) {
                // user is owner of the project
                continue;
            } else {
                boolean found = false;
                Iterator<CmsGroup> itGroups = getGroupsOfUser(dbc, dbc.currentUser().getName(), false).iterator();
                while (itGroups.hasNext()) {
                    CmsGroup group = itGroups.next();
                    if (project.getManagerGroupId().equals(group.getId())) {
                        found = true;
                        break;
                    }
                }
                if (found) {
                    // user is member of the manager group of the project
                    continue;
                }
            }
            itProjects.remove();
        }
        return projects;
    }

    /**
     * Returns all projects which are owned by the current user or which are manageable
     * for the group of the user.<p>
     *
     * @param dbc the current database context
     * @param orgUnit the organizational unit to search project in
     * @param includeSubOus if to include sub organizational units
     *
     * @return a list of objects of type <code>{@link CmsProject}</code>
     *
     * @throws CmsException if operation was not successful
     */
    public List<CmsProject> getAllManageableProjects(
        CmsDbContext dbc,
        CmsOrganizationalUnit orgUnit,
        boolean includeSubOus)
    throws CmsException {

        Set<CmsProject> projects = new HashSet<CmsProject>();

        // get the ous where the user has the project manager role
        List<CmsOrganizationalUnit> ous = getOrgUnitsForRole(
            dbc,
            CmsRole.PROJECT_MANAGER.forOrgUnit(orgUnit.getName()),
            includeSubOus);

        // get the groups of the user if needed
        Set<CmsUUID> userGroupIds = new HashSet<CmsUUID>();
        Iterator<CmsGroup> itGroups = getGroupsOfUser(dbc, dbc.currentUser().getName(), false).iterator();
        while (itGroups.hasNext()) {
            CmsGroup group = itGroups.next();
            userGroupIds.add(group.getId());
        }

        // TODO: this could be optimize if this method would have an additional parameter 'includeSubOus'
        // get all projects that might come in question
        projects.addAll(getProjectDriver(dbc).readProjects(dbc, orgUnit.getName()));

        // filter hidden and not manageable projects
        Iterator<CmsProject> itProjects = projects.iterator();
        while (itProjects.hasNext()) {
            CmsProject project = itProjects.next();
            boolean manageable = true;
            // if online
            manageable = manageable && !project.isOnlineProject();
            // if hidden
            manageable = manageable && !project.isHidden();

            if (!includeSubOus) {
                // if not exact in the given ou
                manageable = manageable && project.getOuFqn().equals(orgUnit.getName());
            } else {
                // if not in the given ou
                manageable = manageable && project.getOuFqn().startsWith(orgUnit.getName());
            }

            if (!manageable) {
                itProjects.remove();
                continue;
            }

            manageable = false;
            // if owner
            manageable = manageable || project.getOwnerId().equals(dbc.currentUser().getId());

            // project managers
            Iterator<CmsOrganizationalUnit> itOus = ous.iterator();
            while (!manageable && itOus.hasNext()) {
                CmsOrganizationalUnit ou = itOus.next();
                // for project managers check visibility
                manageable = manageable || project.getOuFqn().startsWith(ou.getName());
            }

            if (!manageable) {
                // if manager of project
                if (userGroupIds.contains(project.getManagerGroupId())) {
                    String oufqn = readGroup(dbc, project.getManagerGroupId()).getOuFqn();
                    manageable = manageable || (oufqn.startsWith(dbc.getRequestContext().getOuFqn()));
                }
            }
            if (!manageable) {
                // remove not accessible project
                itProjects.remove();
            }
        }

        List<CmsProject> manageableProjects = new ArrayList<CmsProject>(projects);
        // sort the list of projects based on the project name
        Collections.sort(manageableProjects);
        // ensure the online project is not in the list
        CmsProject onlineProject = readProject(dbc, CmsProject.ONLINE_PROJECT_ID);
        if (manageableProjects.contains(onlineProject)) {
            manageableProjects.remove(onlineProject);
        }

        return manageableProjects;
    }

    /**
     * Returns all child groups of a group.<p>
     *
     * @param dbc the current database context
     * @param group the group to get the child for
     * @param includeSubChildren if set also returns all sub-child groups of the given group
     *
     * @return a list of all child <code>{@link CmsGroup}</code> objects
     *
     * @throws CmsException if operation was not successful
     */
    public List<CmsGroup> getChildren(CmsDbContext dbc, CmsGroup group, boolean includeSubChildren)
    throws CmsException {

        if (!includeSubChildren) {
            return getUserDriver(dbc).readChildGroups(dbc, group.getName());
        }
        Set<CmsGroup> allChildren = new TreeSet<CmsGroup>();
        // iterate all child groups
        Iterator<CmsGroup> it = getUserDriver(dbc).readChildGroups(dbc, group.getName()).iterator();
        while (it.hasNext()) {
            CmsGroup child = it.next();
            // add the group itself
            allChildren.add(child);
            // now get all sub-children for each group
            allChildren.addAll(getChildren(dbc, child, true));
        }
        return new ArrayList<CmsGroup>(allChildren);
    }

    /**
     * Returns the date when the resource was last visited by the user.<p>
     *
     * @param dbc the database context
     * @param poolName the name of the database pool to use
     * @param user the user to check the date
     * @param resource the resource to check the date
     *
     * @return the date when the resource was last visited by the user
     *
     * @throws CmsException if something goes wrong
     */
    public long getDateLastVisitedBy(CmsDbContext dbc, String poolName, CmsUser user, CmsResource resource)
    throws CmsException {

        return m_subscriptionDriver.getDateLastVisitedBy(dbc, poolName, user, resource);
    }

    /**
     * Returns all groups of the given organizational unit.<p>
     *
     * @param dbc the current db context
     * @param orgUnit the organizational unit to get the groups for
     * @param includeSubOus if all groups of sub-organizational units should be retrieved too
     * @param readRoles if to read roles or groups
     *
     * @return all <code>{@link CmsGroup}</code> objects in the organizational unit
     *
     * @throws CmsException if operation was not successful
     *
     * @see org.opencms.security.CmsOrgUnitManager#getResourcesForOrganizationalUnit(CmsObject, String)
     * @see org.opencms.security.CmsOrgUnitManager#getGroups(CmsObject, String, boolean)
     */
    public List<CmsGroup> getGroups(
        CmsDbContext dbc,
        CmsOrganizationalUnit orgUnit,
        boolean includeSubOus,
        boolean readRoles)
    throws CmsException {

        return getUserDriver(dbc).getGroups(dbc, orgUnit, includeSubOus, readRoles);
    }

    /**
     * Returns the groups of an user filtered by the specified IP address.<p>
     *
     * @param dbc the current database context
     * @param username the name of the user
     * @param readRoles if to read roles or groups
     *
     * @return the groups of the given user, as a list of {@link CmsGroup} objects
     *
     * @throws CmsException if something goes wrong
     */
    public List<CmsGroup> getGroupsOfUser(CmsDbContext dbc, String username, boolean readRoles) throws CmsException {

        return getGroupsOfUser(dbc, username, "", true, readRoles, false, dbc.getRequestContext().getRemoteAddress());
    }

    /**
     * Returns the groups of an user filtered by the specified IP address.<p>
     *
     * @param dbc the current database context
     * @param username the name of the user
     * @param ouFqn the fully qualified name of the organizational unit to restrict the result set for
     * @param includeChildOus include groups of child organizational units
     * @param readRoles if to read roles or groups
     * @param directGroupsOnly if set only the direct assigned groups will be returned, if not also indirect groups
     * @param remoteAddress the IP address to filter the groups in the result list
     *
     * @return a list of <code>{@link CmsGroup}</code> objects
     *
     * @throws CmsException if operation was not successful
     */
    public List<CmsGroup> getGroupsOfUser(
        CmsDbContext dbc,
        String username,
        String ouFqn,
        boolean includeChildOus,
        boolean readRoles,
        boolean directGroupsOnly,
        String remoteAddress)
    throws CmsException {

        CmsUser user = readUser(dbc, username);
        String prefix = ouFqn + "_" + includeChildOus + "_" + directGroupsOnly + "_" + readRoles + "_" + remoteAddress;
        String cacheKey = m_keyGenerator.getCacheKeyForUserGroups(prefix, dbc, user);
        List<CmsGroup> groups = m_monitor.getCachedUserGroups(cacheKey);
        if (groups == null) {
            // get all groups of the user
            List<CmsGroup> directGroups = getUserDriver(dbc).readGroupsOfUser(
                dbc,
                user.getId(),
                readRoles ? "" : ouFqn,
                readRoles ? true : includeChildOus,
                remoteAddress,
                readRoles);
            Set<CmsGroup> allGroups = new HashSet<CmsGroup>();
            if (!readRoles) {
                allGroups.addAll(directGroups);
            }
            if (!directGroupsOnly) {
                if (!readRoles) {
                    // now get all parents of the groups
                    for (int i = 0; i < directGroups.size(); i++) {
                        CmsGroup parent = getParent(dbc, directGroups.get(i).getName());
                        while ((parent != null) && (!allGroups.contains(parent))) {
                            if (parent.getOuFqn().startsWith(ouFqn)) {
                                allGroups.add(parent);
                            }
                            // read next parent group
                            parent = getParent(dbc, parent.getName());
                        }
                    }
                }
            }
            if (readRoles) {
                // for each for role
                for (int i = 0; i < directGroups.size(); i++) {
                    CmsGroup group = directGroups.get(i);
                    CmsRole role = CmsRole.valueOf(group);
                    if (!includeChildOus && role.getOuFqn().equals(ouFqn)) {
                        allGroups.add(group);
                    }
                    if (includeChildOus && role.getOuFqn().startsWith(ouFqn)) {
                        allGroups.add(group);
                    }
                    if (directGroupsOnly || (!includeChildOus && !role.getOuFqn().equals(ouFqn))) {
                        // if roles of child OUs are not requested and the role does not belong to the requested OU don't include the role children
                        continue;
                    }
                    CmsOrganizationalUnit currentOu = readOrganizationalUnit(dbc, group.getOuFqn());
                    boolean readChildRoleGroups = true;
                    if (currentOu.hasFlagWebuser() && role.forOrgUnit(null).equals(CmsRole.ACCOUNT_MANAGER)) {
                        readChildRoleGroups = false;
                    }
                    if (readChildRoleGroups) {
                        // get the child roles
                        Iterator<CmsRole> itChildRoles = role.getChildren(true).iterator();
                        while (itChildRoles.hasNext()) {
                            CmsRole childRole = itChildRoles.next();
                            if (childRole.isSystemRole()) {
                                if (canReadRoleInOu(currentOu, childRole)) {
                                    // include system roles only
                                    try {
                                        allGroups.add(readGroup(dbc, childRole.getGroupName()));
                                    } catch (CmsDataAccessException e) {
                                        // should not happen, log error if it does
                                        LOG.error(e.getLocalizedMessage(), e);
                                    }
                                }
                            }
                        }
                    } else {
                        LOG.info("Skipping child role group check for web user OU " + currentOu.getName());
                    }
                    if (includeChildOus) {
                        // if needed include the roles of child ous
                        Iterator<CmsOrganizationalUnit> itSubOus = getOrganizationalUnits(
                            dbc,
                            readOrganizationalUnit(dbc, group.getOuFqn()),
                            true).iterator();
                        while (itSubOus.hasNext()) {
                            CmsOrganizationalUnit subOu = itSubOus.next();
                            // add role in child ou
                            try {
                                if (canReadRoleInOu(subOu, role)) {
                                    allGroups.add(readGroup(dbc, role.forOrgUnit(subOu.getName()).getGroupName()));
                                }
                            } catch (CmsDbEntryNotFoundException e) {
                                // ignore, this may happen while deleting an orgunit
                                if (LOG.isDebugEnabled()) {
                                    LOG.debug(e.getLocalizedMessage(), e);
                                }
                            }
                            // add child roles in child ous
                            Iterator<CmsRole> itChildRoles = role.getChildren(true).iterator();
                            while (itChildRoles.hasNext()) {
                                CmsRole childRole = itChildRoles.next();
                                try {
                                    if (canReadRoleInOu(subOu, childRole)) {
                                        allGroups.add(
                                            readGroup(dbc, childRole.forOrgUnit(subOu.getName()).getGroupName()));
                                    }
                                } catch (CmsDbEntryNotFoundException e) {
                                    // ignore, this may happen while deleting an orgunit
                                    if (LOG.isDebugEnabled()) {
                                        LOG.debug(e.getLocalizedMessage(), e);
                                    }
                                }
                            }
                        }
                    }
                }
            }
            // make group list unmodifiable for caching
            groups = Collections.unmodifiableList(new ArrayList<CmsGroup>(allGroups));
            if (dbc.getProjectId().isNullUUID()) {
                m_monitor.cacheUserGroups(cacheKey, groups);
            }
        }

        return groups;
    }

    /**
     * Returns the history driver.<p>
     *
     * @return the history driver
     */
    public I_CmsHistoryDriver getHistoryDriver() {

        return m_historyDriver;
    }

    /**
     * Returns the history driver for a given database context.<p>
     *
     * @param dbc the database context
     * @return the history driver for the database context
     */
    public I_CmsHistoryDriver getHistoryDriver(CmsDbContext dbc) {

        if ((dbc == null) || (dbc.getProjectId() == null) || dbc.getProjectId().isNullUUID()) {
            return m_historyDriver;
        }
        I_CmsHistoryDriver driver = dbc.getHistoryDriver(dbc.getProjectId());
        return driver != null ? driver : m_historyDriver;

    }

    /**
     * Returns the number of idle connections managed by a pool.<p>
     *
     * @param dbPoolUrl the url of a pool
     * @return the number of idle connections
     * @throws CmsDbException if something goes wrong
     */
    public int getIdleConnections(String dbPoolUrl) throws CmsDbException {

        CmsDbPoolV11 pool = m_pools.get(dbPoolUrl);
        if (pool == null) {
            CmsMessageContainer message = Messages.get().container(Messages.ERR_UNKNOWN_POOL_URL_1, dbPoolUrl);
            throw new CmsDbException(message);
        }
        try {
            return pool.getIdleConnections();
        } catch (Exception exc) {
            CmsMessageContainer message = Messages.get().container(Messages.ERR_ACCESSING_POOL_1, dbPoolUrl);
            throw new CmsDbException(message, exc);
        }

    }

    /**
     * Returns the lock state of a resource.<p>
     *
     * @param dbc the current database context
     * @param resource the resource to return the lock state for
     *
     * @return the lock state of the resource
     *
     * @throws CmsException if something goes wrong
     */
    public CmsLock getLock(CmsDbContext dbc, CmsResource resource) throws CmsException {

        return m_lockManager.getLock(dbc, resource);
    }

    /**
     * Returns all locked resources in a given folder.<p>
     *
     * @param dbc the current database context
     * @param resource the folder to search in
     * @param filter the lock filter
     *
     * @return a list of locked resource paths (relative to current site)
     *
     * @throws CmsException if the current project is locked
     */
    public List<String> getLockedResources(CmsDbContext dbc, CmsResource resource, CmsLockFilter filter)
    throws CmsException {

        List<String> lockedResources = new ArrayList<String>();
        // get locked resources
        Iterator<CmsLock> it = m_lockManager.getLocks(dbc, resource.getRootPath(), filter).iterator();
        while (it.hasNext()) {
            CmsLock lock = it.next();
            lockedResources.add(dbc.removeSiteRoot(lock.getResourceName()));
        }
        Collections.sort(lockedResources);
        return lockedResources;
    }

    /**
     * Returns all locked resources in a given folder.<p>
     *
     * @param dbc the current database context
     * @param resource the folder to search in
     * @param filter the lock filter
     *
     * @return a list of locked resources
     *
     * @throws CmsException if the current project is locked
     */
    public List<CmsResource> getLockedResourcesObjects(CmsDbContext dbc, CmsResource resource, CmsLockFilter filter)
    throws CmsException {

        return m_lockManager.getLockedResources(dbc, resource, filter);
    }

    /**
     * Returns all locked resources in a given folder, but uses a cache for resource lookups.<p>
     *
     * @param dbc the current database context
     * @param resource the folder to search in
     * @param filter the lock filter
     * @param cache the cache to use for resource lookups
     *
     * @return a list of locked resources
     *
     * @throws CmsException if the current project is locked
     */
    public List<CmsResource> getLockedResourcesObjectsWithCache(
        CmsDbContext dbc,
        CmsResource resource,
        CmsLockFilter filter,
        Map<String, CmsResource> cache)
    throws CmsException {

        return m_lockManager.getLockedResourcesWithCache(dbc, resource, filter, cache);
    }

    /**
     * Returns all log entries matching the given filter.<p>
     *
     * @param dbc the current db context
     * @param filter the filter to match the log entries
     *
     * @return all log entries matching the given filter
     *
     * @throws CmsException if something goes wrong
     *
     * @see CmsSecurityManager#getLogEntries(CmsRequestContext, CmsLogFilter)
     */
    public List<CmsLogEntry> getLogEntries(CmsDbContext dbc, CmsLogFilter filter) throws CmsException {

        updateLog(dbc);
        return m_projectDriver.readLog(dbc, filter);
    }

    /**
     * Returns the next publish tag for the published historical resources.<p>
     *
     * @param dbc the current database context
     *
     * @return the next available publish tag
     */
    public int getNextPublishTag(CmsDbContext dbc) {

        return getHistoryDriver(dbc).readNextPublishTag(dbc);
    }

    /**
     * Returns all child organizational units of the given parent organizational unit including
     * hierarchical deeper organization units if needed.<p>
     *
     * @param dbc the current db context
     * @param parent the parent organizational unit, or <code>null</code> for the root
     * @param includeChildren if hierarchical deeper organization units should also be returned
     *
     * @return a list of <code>{@link CmsOrganizationalUnit}</code> objects
     *
     * @throws CmsException if operation was not successful
     *
     * @see org.opencms.security.CmsOrgUnitManager#getOrganizationalUnits(CmsObject, String, boolean)
     */
    public List<CmsOrganizationalUnit> getOrganizationalUnits(
        CmsDbContext dbc,
        CmsOrganizationalUnit parent,
        boolean includeChildren)
    throws CmsException {

        if (parent == null) {
            throw new CmsIllegalArgumentException(Messages.get().container(Messages.ERR_PARENT_ORGUNIT_NULL_0));
        }
        return getUserDriver(dbc).getOrganizationalUnits(dbc, parent, includeChildren);
    }

    /**
     * Returns all the organizational units for which the current user has the given role.<p>
     *
     * @param dbc the current database context
     * @param role the role to check
     * @param includeSubOus if sub organizational units should be included in the search
     *
     * @return a list of {@link org.opencms.security.CmsOrganizationalUnit} objects
     *
     * @throws CmsException if something goes wrong
     */
    public List<CmsOrganizationalUnit> getOrgUnitsForRole(CmsDbContext dbc, CmsRole role, boolean includeSubOus)
    throws CmsException {

        String ouFqn = role.getOuFqn();
        if (ouFqn == null) {
            ouFqn = "";
            role = role.forOrgUnit("");
        }
        CmsOrganizationalUnit ou = readOrganizationalUnit(dbc, ouFqn);
        List<CmsOrganizationalUnit> orgUnits = new ArrayList<CmsOrganizationalUnit>();
        if (m_securityManager.hasRole(dbc, dbc.currentUser(), role)) {
            orgUnits.add(ou);
        }
        if (includeSubOus) {
            Iterator<CmsOrganizationalUnit> it = getOrganizationalUnits(dbc, ou, true).iterator();
            while (it.hasNext()) {
                CmsOrganizationalUnit orgUnit = it.next();
                if (m_securityManager.hasRole(dbc, dbc.currentUser(), role.forOrgUnit(orgUnit.getName()))) {
                    orgUnits.add(orgUnit);
                }
            }
        }
        return orgUnits;
    }

    /**
     * Returns the parent group of a group.<p>
     *
     * @param dbc the current database context
     * @param groupname the name of the group
     *
     * @return group the parent group or <code>null</code>
     *
     * @throws CmsException if operation was not successful
     */
    public CmsGroup getParent(CmsDbContext dbc, String groupname) throws CmsException {

        CmsGroup group = readGroup(dbc, groupname);
        if (group.getParentId().isNullUUID()) {
            return null;
        }

        // try to read from cache
        CmsGroup parent = m_monitor.getCachedGroup(group.getParentId().toString());
        if (parent == null) {
            parent = getUserDriver(dbc).readGroup(dbc, group.getParentId());
            m_monitor.cacheGroup(parent);
        }
        return parent;
    }

    /**
     * Returns the set of permissions of the current user for a given resource.<p>
     *
     * @param dbc the current database context
     * @param resource the resource
     * @param user the user
     *
     * @return bit set with allowed permissions
     *
     * @throws CmsException if something goes wrong
     */
    public CmsPermissionSetCustom getPermissions(CmsDbContext dbc, CmsResource resource, CmsUser user)
    throws CmsException {

        CmsAccessControlList acList = getAccessControlList(dbc, resource, false);
        return acList.getPermissions(user, getGroupsOfUser(dbc, user.getName(), false), getRolesForUser(dbc, user));
    }

    /**
     * Returns the project driver.<p>
     *
     * @return the project driver
     */
    public I_CmsProjectDriver getProjectDriver() {

        return m_projectDriver;
    }

    /**
     * Returns the project driver for a given DB context.<p>
     *
     * @param dbc the database context
     *
     * @return the project driver for the database context
     */
    public I_CmsProjectDriver getProjectDriver(CmsDbContext dbc) {

        if ((dbc == null) || (dbc.getProjectId() == null) || dbc.getProjectId().isNullUUID()) {
            return m_projectDriver;
        }
        I_CmsProjectDriver driver = dbc.getProjectDriver(dbc.getProjectId());
        return driver != null ? driver : m_projectDriver;
    }

    /**
     * Returns either the project driver for the DB context (if it has one) or a default project driver.<p>
     *
     * @param dbc the DB context
     * @param defaultDriver the driver which should be returned if there is no project driver for the DB context
     *
     * @return either the project driver for the DB context, or the default driver
     */
    public I_CmsProjectDriver getProjectDriver(CmsDbContext dbc, I_CmsProjectDriver defaultDriver) {

        if ((dbc == null) || (dbc.getProjectId() == null) || dbc.getProjectId().isNullUUID()) {
            return defaultDriver;
        }
        I_CmsProjectDriver driver = dbc.getProjectDriver(dbc.getProjectId());
        return driver != null ? driver : defaultDriver;
    }

    /**
     * Returns the uuid id for the given id.<p>
     *
     * TODO: remove this method as soon as possible
     *
     * @param dbc the current database context
     * @param id the old project id
     *
     * @return the new uuid for the given id
     *
     * @throws CmsException if something goes wrong
     */
    public CmsUUID getProjectId(CmsDbContext dbc, int id) throws CmsException {

        Iterator<CmsProject> itProjects = getAllAccessibleProjects(
            dbc,
            readOrganizationalUnit(dbc, ""),
            true).iterator();
        while (itProjects.hasNext()) {
            CmsProject project = itProjects.next();
            if (project.getUuid().hashCode() == id) {
                return project.getUuid();
            }
        }
        return null;
    }

    /**
     * Returns the configuration read from the <code>opencms.properties</code> file.<p>
     *
     * @return the configuration read from the <code>opencms.properties</code> file
     */
    public CmsParameterConfiguration getPropertyConfiguration() {

        return m_propertyConfiguration;
    }

    /**
     * Returns a new publish list that contains the unpublished resources related
     * to all resources in the given publish list, the related resources exclude
     * all resources in the given publish list and also locked (by other users) resources.<p>
     *
     * @param dbc the current database context
     * @param publishList the publish list to exclude from result
     * @param filter the relation filter to use to get the related resources
     *
     * @return a new publish list that contains the related resources
     *
     * @throws CmsException if something goes wrong
     *
     * @see org.opencms.publish.CmsPublishManager#getRelatedResourcesToPublish(CmsObject, CmsPublishList)
     */
    public CmsPublishList getRelatedResourcesToPublish(
        CmsDbContext dbc,
        CmsPublishList publishList,
        CmsRelationFilter filter)
    throws CmsException {

        Map<String, CmsResource> relations = new HashMap<String, CmsResource>();

        // check if progress should be set in the thread
        A_CmsProgressThread thread = null;
        if (Thread.currentThread() instanceof A_CmsProgressThread) {
            thread = (A_CmsProgressThread)Thread.currentThread();
        }

        // get all resources to publish
        List<CmsResource> publishResources = publishList.getAllResources();
        Iterator<CmsResource> itCheckList = publishResources.iterator();
        // iterate over them
        int count = 0;
        while (itCheckList.hasNext()) {

            // set progress in thread
            count++;
            if (thread != null) {

                if (thread.isInterrupted()) {
                    throw new CmsIllegalStateException(
                        org.opencms.workplace.commons.Messages.get().container(
                            org.opencms.workplace.commons.Messages.ERR_PROGRESS_INTERRUPTED_0));
                }
                thread.setProgress((count * 20) / publishResources.size());
                thread.setDescription(
                    org.opencms.workplace.commons.Messages.get().getBundle().key(
                        org.opencms.workplace.commons.Messages.GUI_PROGRESS_PUBLISH_STEP1_2,
                        new Integer(count),
                        new Integer(publishResources.size())));
            }

            CmsResource checkResource = itCheckList.next();
            // get and iterate over all related resources
            Iterator<CmsRelation> itRelations = getRelationsForResource(dbc, checkResource, filter).iterator();
            while (itRelations.hasNext()) {
                CmsRelation relation = itRelations.next();
                try {
                    // get the target of the relation, see CmsRelation#getTarget(CmsObject, CmsResourceFilter)
                    CmsResource target;
                    try {
                        // first look up by id
                        target = readResource(dbc, relation.getTargetId(), CmsResourceFilter.ALL);
                    } catch (CmsVfsResourceNotFoundException e) {
                        // then look up by name, but from the root site
                        String storedSiteRoot = dbc.getRequestContext().getSiteRoot();
                        try {
                            dbc.getRequestContext().setSiteRoot("");
                            target = readResource(dbc, relation.getTargetPath(), CmsResourceFilter.ALL);
                        } finally {
                            dbc.getRequestContext().setSiteRoot(storedSiteRoot);
                        }
                    }
                    CmsLock lock = getLock(dbc, target);
                    // just add resources that may come in question
                    if (!publishResources.contains(target) // is not in the original list
                        && !relations.containsKey(target.getRootPath()) // has not been already added by another relation
                        && !target.getState().isUnchanged() // has been changed
                        && lock.isLockableBy(dbc.currentUser())) { // is lockable by current user

                        relations.put(target.getRootPath(), target);
                        // now check the folder structure
                        CmsResource parent = getVfsDriver(dbc).readParentFolder(
                            dbc,
                            dbc.currentProject().getUuid(),
                            target.getStructureId());
                        while ((parent != null) && parent.getState().isNew()) {
                            // just add resources that may come in question
                            if (!publishResources.contains(parent) // is not in the original list
                                && !relations.containsKey(parent.getRootPath())) { // has not been already added by another relation

                                relations.put(parent.getRootPath(), parent);
                            }
                            parent = getVfsDriver(dbc).readParentFolder(
                                dbc,
                                dbc.currentProject().getUuid(),
                                parent.getStructureId());
                        }
                    }
                } catch (CmsVfsResourceNotFoundException e) {
                    // ignore broken links
                    if (LOG.isDebugEnabled()) {
                        LOG.debug(e.getLocalizedMessage(), e);
                    }
                }
            }
        }

        CmsPublishList ret = new CmsPublishList(publishList.getDirectPublishResources(), false, false);
        ret.addAll(relations.values(), false);
        ret.initialize();
        return ret;
    }

    /**
     * Returns all relations for the given resource matching the given filter.<p>
     *
     * @param dbc the current db context
     * @param resource the resource to retrieve the relations for
     * @param filter the filter to match the relation
     *
     * @return all relations for the given resource matching the given filter
     *
     * @throws CmsException if something goes wrong
     *
     * @see CmsSecurityManager#getRelationsForResource(CmsRequestContext, CmsResource, CmsRelationFilter)
     */
    public List<CmsRelation> getRelationsForResource(CmsDbContext dbc, CmsResource resource, CmsRelationFilter filter)
    throws CmsException {

        CmsUUID projectId = getProjectIdForContext(dbc);
        return getVfsDriver(dbc).readRelations(dbc, projectId, resource, filter);
    }

    /**
     * Returns the list of organizational units the given resource belongs to.<p>
     *
     * @param dbc the current database context
     * @param resource the resource
     *
     * @return list of {@link CmsOrganizationalUnit} objects
     *
     * @throws CmsException if something goes wrong
     */
    public List<CmsOrganizationalUnit> getResourceOrgUnits(CmsDbContext dbc, CmsResource resource) throws CmsException {

        List<CmsOrganizationalUnit> result = getVfsDriver(dbc).getResourceOus(
            dbc,
            dbc.currentProject().getUuid(),
            resource);

        return result;
    }

    /**
     * Returns all resources of the given organizational unit.<p>
     *
     * @param dbc the current db context
     * @param orgUnit the organizational unit to get all resources for
     *
     * @return all <code>{@link CmsResource}</code> objects in the organizational unit
     *
     * @throws CmsException if operation was not successful
     *
     * @see org.opencms.security.CmsOrgUnitManager#getResourcesForOrganizationalUnit(CmsObject, String)
     * @see org.opencms.security.CmsOrgUnitManager#getUsers(CmsObject, String, boolean)
     * @see org.opencms.security.CmsOrgUnitManager#getGroups(CmsObject, String, boolean)
     */
    public List<CmsResource> getResourcesForOrganizationalUnit(CmsDbContext dbc, CmsOrganizationalUnit orgUnit)
    throws CmsException {

        return getUserDriver(dbc).getResourcesForOrganizationalUnit(dbc, orgUnit);
    }

    /**
     * Returns all resources associated to a given principal via an ACE with the given permissions.<p>
     *
     * If the <code>includeAttr</code> flag is set it returns also all resources associated to
     * a given principal through some of following attributes.<p>
     *
     * <ul>
     *    <li>User Created</li>
     *    <li>User Last Modified</li>
     * </ul><p>
     *
     * @param dbc the current database context
     * @param project the to read the entries from
     * @param principalId the id of the principal
     * @param permissions a set of permissions to match, can be <code>null</code> for all ACEs
     * @param includeAttr a flag to include resources associated by attributes
     *
     * @return a set of <code>{@link CmsResource}</code> objects
     *
     * @throws CmsException if something goes wrong
     */
    public Set<CmsResource> getResourcesForPrincipal(
        CmsDbContext dbc,
        CmsProject project,
        CmsUUID principalId,
        CmsPermissionSet permissions,
        boolean includeAttr)
    throws CmsException {

        Set<CmsResource> resources = new HashSet<CmsResource>(
            getVfsDriver(dbc).readResourcesForPrincipalACE(dbc, project, principalId));
        if (permissions != null) {
            Iterator<CmsResource> itRes = resources.iterator();
            while (itRes.hasNext()) {
                CmsAccessControlEntry ace = readAccessControlEntry(dbc, itRes.next(), principalId);
                if ((ace.getPermissions().getPermissions()
                    & permissions.getPermissions()) != permissions.getPermissions()) {
                    // remove if permissions does not match
                    itRes.remove();
                }
            }
        }
        if (includeAttr) {
            resources.addAll(getVfsDriver(dbc).readResourcesForPrincipalAttr(dbc, project, principalId));
        }
        return resources;
    }

    /**
     * Gets the rewrite aliases matching a given filter.<p>
     *
     * @param dbc the current database context
     * @param filter the filter used for filtering rewrite aliases
     *
     * @return the rewrite aliases matching the given filter
     *
     * @throws CmsException if something goes wrong
     */
    public List<CmsRewriteAlias> getRewriteAliases(CmsDbContext dbc, CmsRewriteAliasFilter filter) throws CmsException {

        return getVfsDriver(dbc).readRewriteAliases(dbc, filter);
    }

    /**
     * Collects the groups which constitute a given role.<p>
     *
     * @param dbc the database context
     * @param roleGroupName the group related to the role
     * @param directUsersOnly if true, only the group belonging to the entry itself wil
     *
     * @return the set of groups which constitute the role
     *
     * @throws CmsException if something goes wrong
     */
    public Set<CmsGroup> getRoleGroups(CmsDbContext dbc, String roleGroupName, boolean directUsersOnly)
    throws CmsException {

        return getRoleGroupsImpl(dbc, roleGroupName, directUsersOnly, new HashMap<String, Set<CmsGroup>>());
    }

    /**
     * Collects the groups which constitute a given role.<p>
     *
     * @param dbc the database context
     * @param roleGroupName the group related to the role
     * @param directUsersOnly if true, only the group belonging to the entry itself wil
     * @param accumulator a map for memoizing return values of recursive calls
     *
     * @return the set of groups which constitute the role
     *
     * @throws CmsException if something goes wrong
     */
    public Set<CmsGroup> getRoleGroupsImpl(
        CmsDbContext dbc,
        String roleGroupName,
        boolean directUsersOnly,
        Map<String, Set<CmsGroup>> accumulator)
    throws CmsException {

        Set<CmsGroup> result = new HashSet<CmsGroup>();
        if (accumulator.get(roleGroupName) != null) {
            return accumulator.get(roleGroupName);
        }
        CmsGroup group = readGroup(dbc, roleGroupName); // check that the group really exists
        if ((group == null) || (!group.isRole())) {
            throw new CmsDbEntryNotFoundException(
                Messages.get().container(Messages.ERR_UNKNOWN_GROUP_1, roleGroupName));
        }
        result.add(group);
        if (!directUsersOnly) {
            CmsRole role = CmsRole.valueOf(group);
            if (role.getParentRole() != null) {
                try {
                    String parentGroup = role.getParentRole().getGroupName();
                    // iterate the parent roles
                    result.addAll(getRoleGroupsImpl(dbc, parentGroup, directUsersOnly, accumulator));
                } catch (CmsDbEntryNotFoundException e) {
                    // ignore, this may happen while deleting an orgunit
                    if (LOG.isDebugEnabled()) {
                        LOG.debug(e.getLocalizedMessage(), e);
                    }
                }
            }
            String parentOu = CmsOrganizationalUnit.getParentFqn(group.getOuFqn());
            if (parentOu != null) {
                // iterate the parent ou's
                result.addAll(getRoleGroupsImpl(dbc, parentOu + group.getSimpleName(), directUsersOnly, accumulator));
            }
        }
        accumulator.put(roleGroupName, result);
        return result;
    }

    /**
     * Returns all roles the given user has for the given resource.<p>
     *
     * @param dbc the current database context
     * @param user the user to check
     * @param resource the resource to check the roles for
     *
     * @return a list of {@link CmsRole} objects
     *
     * @throws CmsException if something goes wrong
     */
    public List<CmsRole> getRolesForResource(CmsDbContext dbc, CmsUser user, CmsResource resource) throws CmsException {

        // guest user has no role
        if (user.isGuestUser()) {
            return Collections.emptyList();
        }

        // try to read from cache
        String key = user.getId().toString() + resource.getRootPath();
        List<CmsRole> result = m_monitor.getCachedRoleList(key);
        if (result != null) {
            return result;
        }
        result = new ArrayList<CmsRole>();

        Iterator<CmsOrganizationalUnit> itOus = getResourceOrgUnits(dbc, resource).iterator();
        while (itOus.hasNext()) {
            CmsOrganizationalUnit ou = itOus.next();

            // read all roles of the current user
            List<CmsGroup> groups = new ArrayList<CmsGroup>(
                getGroupsOfUser(
                    dbc,
                    user.getName(),
                    ou.getName(),
                    false,
                    true,
                    false,
                    dbc.getRequestContext().getRemoteAddress()));
            // check the roles applying to the given resource
            Iterator<CmsGroup> it = groups.iterator();
            while (it.hasNext()) {
                CmsGroup group = it.next();
                CmsRole givenRole = CmsRole.valueOf(group).forOrgUnit(null);
                if (givenRole.isOrganizationalUnitIndependent() || result.contains(givenRole)) {
                    // skip already added roles
                    continue;
                }
                result.add(givenRole);
            }
        }

        result = Collections.unmodifiableList(result);
        m_monitor.cacheRoleList(key, result);
        return result;
    }

    /**
     * Returns all roles the given user has independent of the resource.<p>
     *
     * @param dbc the current database context
     * @param user the user to check
     *
     * @return a list of {@link CmsRole} objects
     *
     * @throws CmsException if something goes wrong
     */
    public List<CmsRole> getRolesForUser(CmsDbContext dbc, CmsUser user) throws CmsException {

        // guest user has no role
        if (user.isGuestUser()) {
            return Collections.emptyList();
        }

        // try to read from cache
        String key = user.getId().toString();
        List<CmsRole> result = m_monitor.getCachedRoleList(key);
        if (result != null) {
            return result;
        }
        result = new ArrayList<CmsRole>();

        // read all roles of the current user
        List<CmsGroup> groups = new ArrayList<CmsGroup>(
            getGroupsOfUser(dbc, user.getName(), "", true, true, false, dbc.getRequestContext().getRemoteAddress()));

        // check the roles applying to the given resource
        Iterator<CmsGroup> it = groups.iterator();
        while (it.hasNext()) {
            CmsGroup group = it.next();
            CmsRole givenRole = CmsRole.valueOf(group);
            givenRole = givenRole.forOrgUnit(null);
            if (!result.contains(givenRole)) {
                result.add(givenRole);
            }
        }
        result = Collections.unmodifiableList(result);
        m_monitor.cacheRoleList(key, result);
        return result;
    }

    /**
     * Returns the security manager this driver manager belongs to.<p>
     *
     * @return the security manager this driver manager belongs to
     */
    public CmsSecurityManager getSecurityManager() {

        return m_securityManager;
    }

    /**
     * Returns an instance of the common sql manager.<p>
     *
     * @return an instance of the common sql manager
     */
    public CmsSqlManager getSqlManager() {

        return m_sqlManager;
    }

    /**
     * Returns the subscription driver of this driver manager.<p>
     *
     * @return a subscription driver
     */
    public I_CmsSubscriptionDriver getSubscriptionDriver() {

        return m_subscriptionDriver;
    }

    /**
     * Returns the user driver.<p>
     *
     * @return the user driver
     */
    public I_CmsUserDriver getUserDriver() {

        return m_userDriver;
    }

    /**
     * Returns the user driver for a given database context.<p>
     *
     * @param dbc the database context
     *
     * @return the user driver for the database context
     */
    public I_CmsUserDriver getUserDriver(CmsDbContext dbc) {

        if ((dbc == null) || (dbc.getProjectId() == null) || dbc.getProjectId().isNullUUID()) {
            return m_userDriver;
        }
        I_CmsUserDriver driver = dbc.getUserDriver(dbc.getProjectId());
        return driver != null ? driver : m_userDriver;

    }

    /**
     * Returns either the user driver for the given DB context (if it has one) or a default value instead.<p>
     *
     * @param dbc the DB context
     * @param defaultDriver the driver that should be returned if no driver for the DB context was found
     *
     * @return either the user driver for the DB context, or <code>defaultDriver</code> if none were found
     */
    public I_CmsUserDriver getUserDriver(CmsDbContext dbc, I_CmsUserDriver defaultDriver) {

        if ((dbc == null) || (dbc.getProjectId() == null) || dbc.getProjectId().isNullUUID()) {
            return defaultDriver;
        }
        I_CmsUserDriver driver = dbc.getUserDriver(dbc.getProjectId());
        return driver != null ? driver : defaultDriver;
    }

    /**
     * Returns all direct users of the given organizational unit.<p>
     *
     * @param dbc the current db context
     * @param orgUnit the organizational unit to get all users for
     * @param recursive if all groups of sub-organizational units should be retrieved too
     *
     * @return all <code>{@link CmsUser}</code> objects in the organizational unit
     *
     * @throws CmsException if operation was not successful
     *
     * @see org.opencms.security.CmsOrgUnitManager#getResourcesForOrganizationalUnit(CmsObject, String)
     * @see org.opencms.security.CmsOrgUnitManager#getUsers(CmsObject, String, boolean)
     */
    public List<CmsUser> getUsers(CmsDbContext dbc, CmsOrganizationalUnit orgUnit, boolean recursive)
    throws CmsException {

        return getUserDriver(dbc).getUsers(dbc, orgUnit, recursive);
    }

    /**
     * Returns a list of users in a group.<p>
     *
     * @param dbc the current database context
     * @param groupname the name of the group to list users from
     * @param includeOtherOuUsers include users of other organizational units
     * @param directUsersOnly if set only the direct assigned users will be returned,
     *                        if not also indirect users, ie. members of parent roles,
     *                        this parameter only works with roles
     * @param readRoles if to read roles or groups
     *
     * @return all <code>{@link CmsUser}</code> objects in the group
     *
     * @throws CmsException if operation was not successful
     */
    public List<CmsUser> getUsersOfGroup(
        CmsDbContext dbc,
        String groupname,
        boolean includeOtherOuUsers,
        boolean directUsersOnly,
        boolean readRoles)
    throws CmsException {

        return internalUsersOfGroup(
            dbc,
            CmsOrganizationalUnit.getParentFqn(groupname),
            groupname,
            includeOtherOuUsers,
            directUsersOnly,
            readRoles);
    }

    /**
     * Returns the given user's publish list.<p>
     *
     * @param dbc the database context
     * @param userId the user's id
     *
     * @return the given user's publish list
     *
     * @throws CmsDataAccessException if something goes wrong
     */
    public List<CmsResource> getUsersPubList(CmsDbContext dbc, CmsUUID userId) throws CmsDataAccessException {

        synchronized (m_publishListUpdateLock) {
            updateLog(dbc);
            return m_projectDriver.getUsersPubList(dbc, userId);
        }
    }

    /**
     * Returns all direct users of the given organizational unit, without their additional info.<p>
     *
     * @param dbc the current db context
     * @param orgUnit the organizational unit to get all users for
     * @param recursive if all groups of sub-organizational units should be retrieved too
     *
     * @return all <code>{@link CmsUser}</code> objects in the organizational unit
     *
     * @throws CmsException if operation was not successful
     *
     * @see org.opencms.security.CmsOrgUnitManager#getResourcesForOrganizationalUnit(CmsObject, String)
     * @see org.opencms.security.CmsOrgUnitManager#getUsers(CmsObject, String, boolean)
     */
    public List<CmsUser> getUsersWithoutAdditionalInfo(
        CmsDbContext dbc,
        CmsOrganizationalUnit orgUnit,
        boolean recursive)
    throws CmsException {

        return getUserDriver(dbc).getUsersWithoutAdditionalInfo(dbc, orgUnit, recursive);
    }

    /**
     * Returns the VFS driver.<p>
     *
     * @return the VFS driver
     */
    public I_CmsVfsDriver getVfsDriver() {

        return m_vfsDriver;
    }

    /**
     * Returns the VFS driver for the given database context.<p>
     *
     * @param dbc the database context
     *
     * @return a VFS driver
     */
    public I_CmsVfsDriver getVfsDriver(CmsDbContext dbc) {

        if ((dbc == null) || (dbc.getProjectId() == null) || dbc.getProjectId().isNullUUID()) {
            return m_vfsDriver;
        }
        I_CmsVfsDriver driver = dbc.getVfsDriver(dbc.getProjectId());
        return driver != null ? driver : m_vfsDriver;

    }

    /**
     * Writes a vector of access control entries as new access control entries of a given resource.<p>
     *
     * Already existing access control entries of this resource are removed before.
     * Access is granted, if:<p>
     * <ul>
     * <li>the current user has control permission on the resource</li>
     * </ul>
     *
     * @param dbc the current database context
     * @param resource the resource
     * @param acEntries a list of <code>{@link CmsAccessControlEntry}</code> objects
     *
     * @throws CmsException if something goes wrong
     */
    public void importAccessControlEntries(
        CmsDbContext dbc,
        CmsResource resource,
        List<CmsAccessControlEntry> acEntries)
    throws CmsException {

        I_CmsUserDriver userDriver = getUserDriver(dbc);
        userDriver.removeAccessControlEntries(dbc, dbc.currentProject(), resource.getResourceId());
        List<CmsAccessControlEntry> fixedAces = new ArrayList<>();
        for (CmsAccessControlEntry entry : acEntries) {
            if (entry.getResource() == null) {
                entry = new CmsAccessControlEntry(
                    resource.getResourceId(),
                    entry.getPrincipal(),
                    entry.getPermissions(),
                    entry.getFlags());
            }
            fixedAces.add(entry);
        }

        Iterator<CmsAccessControlEntry> i = fixedAces.iterator();
        while (i.hasNext()) {
            userDriver.writeAccessControlEntry(dbc, dbc.currentProject(), i.next());
        }
        m_monitor.clearAccessControlListCache();
    }

    /**
     * Imports a rewrite alias.<p>
     *
     * @param dbc the database context
     * @param siteRoot the site root of the alias
     * @param source the source of the alias
     * @param target the target of the alias
     * @param mode the alias mode
     *
     * @return the import result
     *
     * @throws CmsException if something goes wrong
     */
    public CmsAliasImportResult importRewriteAlias(
        CmsDbContext dbc,
        String siteRoot,
        String source,
        String target,
        CmsAliasMode mode)
    throws CmsException {

        I_CmsVfsDriver vfs = getVfsDriver(dbc);
        List<CmsRewriteAlias> existingAliases = vfs.readRewriteAliases(
            dbc,
            new CmsRewriteAliasFilter().setSiteRoot(siteRoot));
        CmsUUID idToDelete = null;
        for (CmsRewriteAlias alias : existingAliases) {
            if (alias.getPatternString().equals(source)) {
                idToDelete = alias.getId();
            }
        }
        if (idToDelete != null) {
            vfs.deleteRewriteAliases(dbc, new CmsRewriteAliasFilter().setId(idToDelete));
        }
        CmsRewriteAlias alias = new CmsRewriteAlias(new CmsUUID(), siteRoot, source, target, mode);
        List<CmsRewriteAlias> aliases = new ArrayList<CmsRewriteAlias>();
        aliases.add(alias);
        getVfsDriver(dbc).insertRewriteAliases(dbc, aliases);
        CmsAliasImportResult result = new CmsAliasImportResult(
            CmsAliasImportStatus.aliasNew,
            "OK",
            source,
            target,
            mode);
        return result;
    }

    /**
     * Creates a new user by import.<p>
     *
     * @param dbc the current database context
     * @param id the id of the user
     * @param name the new name for the user
     * @param password the new password for the user (already encrypted)
     * @param firstname the firstname of the user
     * @param lastname the lastname of the user
     * @param email the email of the user
     * @param flags the flags for a user (for example <code>{@link I_CmsPrincipal#FLAG_ENABLED}</code>)
     * @param dateCreated the creation date
     * @param additionalInfos the additional user infos
     *
     * @return the imported user
     *
     * @throws CmsException if something goes wrong
     */
    public CmsUser importUser(
        CmsDbContext dbc,
        String id,
        String name,
        String password,
        String firstname,
        String lastname,
        String email,
        int flags,
        long dateCreated,
        Map<String, Object> additionalInfos)
    throws CmsException {

        // no space before or after the name
        name = name.trim();
        // check the user name
        String userName = CmsOrganizationalUnit.getSimpleName(name);
        OpenCms.getValidationHandler().checkUserName(userName);
        if (CmsStringUtil.isEmptyOrWhitespaceOnly(userName)) {
            throw new CmsIllegalArgumentException(Messages.get().container(Messages.ERR_BAD_USER_1, userName));
        }
        // check the ou
        CmsOrganizationalUnit ou = readOrganizationalUnit(dbc, CmsOrganizationalUnit.getParentFqn(name));

        // check webuser ou
        if (ou.hasFlagWebuser() && ((flags & I_CmsPrincipal.FLAG_USER_WEBUSER) == 0)) {
            flags += I_CmsPrincipal.FLAG_USER_WEBUSER;
        }
        CmsUser newUser = getUserDriver(dbc).createUser(
            dbc,
            new CmsUUID(id),
            name,
            password,
            firstname,
            lastname,
            email,
            0,
            flags,
            dateCreated,
            additionalInfos);
        return newUser;
    }

    /**
     * Increments a counter and returns its value before incrementing.<p>
     *
     * @param dbc the current database context
     * @param name the name of the counter which should be incremented
     *
     * @return the value of the counter
     *
     * @throws CmsException if something goes wrong
     */
    public int incrementCounter(CmsDbContext dbc, String name) throws CmsException {

        return getVfsDriver(dbc).incrementCounter(dbc, name);
    }

    /**
     * Initializes the driver and sets up all required modules and connections.<p>
     *
     * @param configurationManager the configuration manager
     * @param dbContextFactory the db context factory
     *
     * @throws CmsException if something goes wrong
     * @throws Exception if something goes wrong
     */
    public void init(CmsConfigurationManager configurationManager, I_CmsDbContextFactory dbContextFactory)
    throws CmsException, Exception {

        // initialize the access-module.
        if (CmsLog.INIT.isInfoEnabled()) {
            CmsLog.INIT.info(Messages.get().getBundle().key(Messages.INIT_DRIVER_MANAGER_START_PHASE4_0));
        }
        // store local reference to the memory monitor to avoid multiple lookups through the OpenCms singelton
        m_monitor = OpenCms.getMemoryMonitor();

        CmsSystemConfiguration systemConfiguation = (CmsSystemConfiguration)configurationManager.getConfiguration(
            CmsSystemConfiguration.class);
        CmsCacheSettings settings = systemConfiguation.getCacheSettings();

        // initialize the key generator
        m_keyGenerator = (I_CmsCacheKey)Class.forName(settings.getCacheKeyGenerator()).newInstance();

        // initialize the HTML link validator
        m_htmlLinkValidator = new CmsRelationSystemValidator(this);

        // fills the defaults if needed
        CmsDbContext dbc1 = dbContextFactory.getDbContext();
        getUserDriver().fillDefaults(dbc1);
        getProjectDriver().fillDefaults(dbc1);

        // set the driver manager in the publish engine
        m_publishEngine.setDriverManager(this);
        // create the root organizational unit if needed
        CmsDbContext dbc2 = dbContextFactory.getDbContext(
            new CmsRequestContext(
                readUser(dbc1, OpenCms.getDefaultUsers().getUserAdmin()),
                readProject(dbc1, CmsProject.ONLINE_PROJECT_ID),
                null,
                CmsSiteMatcher.DEFAULT_MATCHER,
                "",
                false,
                null,
                null,
                null,
                0,
                null,
                null,
                "",
                false));
        dbc1.clear();
        getUserDriver().createRootOrganizationalUnit(dbc2);
        dbc2.clear();
    }

    /**
     * Initializes the organizational unit.<p>
     *
     * @param dbc the DB context
     * @param ou the organizational unit
     */
    public void initOrgUnit(CmsDbContext dbc, CmsOrganizationalUnit ou) {

        try {
            dbc.setAttribute(ATTR_INIT_OU, ou);
            m_userDriver.fillDefaults(dbc);
        } finally {
            dbc.removeAttribute(ATTR_INIT_OU);
        }
    }

    /**
     * Checks if the specified resource is inside the current project.<p>
     *
     * The project "view" is determined by a set of path prefixes.
     * If the resource starts with any one of this prefixes, it is considered to
     * be "inside" the project.<p>
     *
     * @param dbc the current database context
     * @param resourcename the specified resource name (full path)
     *
     * @return <code>true</code>, if the specified resource is inside the current project
     */
    public boolean isInsideCurrentProject(CmsDbContext dbc, String resourcename) {

        List<String> projectResources = null;
        try {
            projectResources = readProjectResources(dbc, dbc.currentProject());
        } catch (CmsException e) {
            if (LOG.isErrorEnabled()) {
                LOG.error(
                    Messages.get().getBundle().key(
                        Messages.LOG_CHECK_RESOURCE_INSIDE_CURRENT_PROJECT_2,
                        resourcename,
                        dbc.currentProject().getName()),
                    e);
            }
            return false;
        }
        return CmsProject.isInsideProject(projectResources, resourcename);
    }

    /**
     * Checks whether the subscription driver is available.<p>
     *
     * @return true if the subscription driver is available
     */
    public boolean isSubscriptionDriverAvailable() {

        return m_subscriptionDriver != null;
    }

    /**
     * Checks if a project is the tempfile project.<p>
     * @param project the project to test
     * @return true if the project is the tempfile project
     */
    public boolean isTempfileProject(CmsProject project) {

        return project.getName().equals("tempFileProject");
    }

    /**
     * Checks if one of the resources (except the resource itself)
     * is a sibling in a "labeled" site folder.<p>
     *
     * This method is used when creating a new sibling
     * (use the <code>newResource</code> parameter & <code>action = 1</code>)
     * or deleting/importing a resource (call with <code>action = 2</code>).<p>
     *
     * @param dbc the current database context
     * @param resource the resource
     * @param newResource absolute path for a resource sibling which will be created
     * @param action the action which has to be performed (1: create VFS link, 2: all other actions)
     *
     * @return <code>true</code> if the flag should be set for the resource, otherwise <code>false</code>
     *
     * @throws CmsDataAccessException if something goes wrong
     */
    public boolean labelResource(CmsDbContext dbc, CmsResource resource, String newResource, int action)
    throws CmsDataAccessException {

        // get the list of labeled site folders from the runtime property
        List<String> labeledSites = OpenCms.getWorkplaceManager().getLabelSiteFolders();

        if (labeledSites.size() == 0) {
            // no labeled sites defined, just return false
            return false;
        }

        if (action == 1) {
            // CASE 1: a new resource is created, check the sites
            if (!resource.isLabeled()) {
                // source isn't labeled yet, so check!
                boolean linkInside = false;
                boolean sourceInside = false;
                for (int i = 0; i < labeledSites.size(); i++) {
                    String curSite = labeledSites.get(i);
                    if (newResource.startsWith(curSite)) {
                        // the link lies in a labeled site
                        linkInside = true;
                    }
                    if (resource.getRootPath().startsWith(curSite)) {
                        // the source lies in a labeled site
                        sourceInside = true;
                    }
                    if (linkInside && sourceInside) {
                        break;
                    }
                }
                // return true when either source or link is in labeled site, otherwise false
                return (linkInside != sourceInside);
            }
            // resource is already labeled
            return false;

        } else {
            // CASE 2: the resource will be deleted or created (import)
            // check if at least one of the other siblings resides inside a "labeled site"
            // and if at least one of the other siblings resides outside a "labeled site"
            boolean isInside = false;
            boolean isOutside = false;
            // check if one of the other vfs links lies in a labeled site folder
            List<CmsResource> siblings = getVfsDriver(
                dbc).readSiblings(dbc, dbc.currentProject().getUuid(), resource, false);
            updateContextDates(dbc, siblings);
            Iterator<CmsResource> i = siblings.iterator();
            while (i.hasNext() && (!isInside || !isOutside)) {
                CmsResource currentResource = i.next();
                if (currentResource.equals(resource)) {
                    // dont't check the resource itself!
                    continue;
                }
                String curPath = currentResource.getRootPath();
                boolean curInside = false;
                for (int k = 0; k < labeledSites.size(); k++) {
                    if (curPath.startsWith(labeledSites.get(k))) {
                        // the link is in the labeled site
                        isInside = true;
                        curInside = true;
                        break;
                    }
                }
                if (!curInside) {
                    // the current link was not found in labeled site, so it is outside
                    isOutside = true;
                }
            }
            // now check the new resource name if present
            if (newResource != null) {
                boolean curInside = false;
                for (int k = 0; k < labeledSites.size(); k++) {
                    if (newResource.startsWith(labeledSites.get(k))) {
                        // the new resource is in the labeled site
                        isInside = true;
                        curInside = true;
                        break;
                    }
                }
                if (!curInside) {
                    // the new resource was not found in labeled site, so it is outside
                    isOutside = true;
                }
            }
            return (isInside && isOutside);
        }
    }

    /**
     * Returns the user, who had locked the resource.<p>
     *
     * A user can lock a resource, so he is the only one who can write this
     * resource. This methods checks, if a resource was locked.
     *
     * @param dbc the current database context
     * @param resource the resource
     *
     * @return the user, who had locked the resource
     *
     * @throws CmsException will be thrown, if the user has not the rights for this resource
     */
    public CmsUser lockedBy(CmsDbContext dbc, CmsResource resource) throws CmsException {

        return readUser(dbc, m_lockManager.getLock(dbc, resource).getEditionLock().getUserId());
    }

    /**
     * Locks a resource.<p>
     *
     * The <code>type</code> parameter controls what kind of lock is used.<br>
     * Possible values for this parameter are: <br>
     * <ul>
     * <li><code>{@link org.opencms.lock.CmsLockType#EXCLUSIVE}</code></li>
     * <li><code>{@link org.opencms.lock.CmsLockType#TEMPORARY}</code></li>
     * <li><code>{@link org.opencms.lock.CmsLockType#PUBLISH}</code></li>
     * </ul><p>
     *
     * @param dbc the current database context
     * @param resource the resource to lock
     * @param type type of the lock
     *
     * @throws CmsException if something goes wrong
     *
     * @see CmsObject#lockResource(String)
     * @see CmsObject#lockResourceTemporary(String)
     * @see org.opencms.file.types.I_CmsResourceType#lockResource(CmsObject, CmsSecurityManager, CmsResource, CmsLockType)
     */
    public void lockResource(CmsDbContext dbc, CmsResource resource, CmsLockType type) throws CmsException {

        // update the resource cache
        m_monitor.clearResourceCache();

        CmsProject project = dbc.currentProject();

        // add the resource to the lock dispatcher
        m_lockManager.addResource(dbc, resource, dbc.currentUser(), project, type);
        boolean changedProjectLastModified = false;
        if (!resource.getState().isUnchanged() && !resource.getState().isKeep()) {
            // update the project flag of a modified resource as "last modified inside the current project"
            getVfsDriver(dbc).writeLastModifiedProjectId(dbc, project, project.getUuid(), resource);
            changedProjectLastModified = true;
        }

        // we must also clear the permission cache
        m_monitor.flushCache(CmsMemoryMonitor.CacheType.PERMISSION);

        // fire resource modification event
        Map<String, Object> data = new HashMap<String, Object>(2);
        data.put(I_CmsEventListener.KEY_RESOURCE, resource);
        data.put(
            I_CmsEventListener.KEY_CHANGE,
            new Integer(changedProjectLastModified ? CHANGED_PROJECT : NOTHING_CHANGED));
        data.put(I_CmsEventListener.KEY_SKIPINDEX, Boolean.TRUE);
        OpenCms.fireCmsEvent(new CmsEvent(I_CmsEventListener.EVENT_RESOURCE_MODIFIED, data));
    }

    /**
     * Adds the given log entry to the current user's log.<p>
     *
     * This operation works only on memory, to get the log entries actually
     * written to DB you have to call the {@link #updateLog(CmsDbContext)} method.<p>
     *
     * @param dbc the current database context
     * @param logEntry the log entry to create
     * @param force forces the log entry to be counted,
     *              if not only the first log entry in a transaction will be taken into account
     */
    public void log(CmsDbContext dbc, CmsLogEntry logEntry, boolean force) {

        if (dbc == null) {
            return;
        }
        // check log level
        if (!logEntry.getType().isActive()) {
            // do not log inactive entries
            return;
        }
        // if not forcing
        if (!force) {
            // operation already logged
            boolean abort = (dbc.getAttribute(CmsLogEntry.ATTR_LOG_ENTRY) != null);
            // disabled logging from outside
            abort |= (dbc.getRequestContext().getAttribute(CmsLogEntry.ATTR_LOG_ENTRY) != null);
            if (abort) {
                return;
            }
        }
        // prevent several entries for the same operation
        dbc.setAttribute(CmsLogEntry.ATTR_LOG_ENTRY, Boolean.TRUE);
        // keep it for later
        m_log.add(logEntry);
    }

    /**
     * Attempts to authenticate a user into OpenCms with the given password.<p>
     *
     * @param dbc the current database context
     * @param userName the name of the user to be logged in
     * @param password the password of the user
     * @param remoteAddress the ip address of the request
     *
     * @return the logged in user
     *
     * @throws CmsAuthentificationException if the login was not successful
     * @throws CmsDataAccessException in case of errors accessing the database
     * @throws CmsPasswordEncryptionException in case of errors encrypting the users password
     */
    public CmsUser loginUser(CmsDbContext dbc, String userName, String password, String remoteAddress)
    throws CmsAuthentificationException, CmsDataAccessException, CmsPasswordEncryptionException {

        if (CmsStringUtil.isEmptyOrWhitespaceOnly(password)) {
            throw new CmsDbEntryNotFoundException(Messages.get().container(Messages.ERR_UNKNOWN_USER_1, userName));
        }
        CmsUser newUser;
        try {
            // read the user from the driver to avoid the cache
            newUser = getUserDriver(dbc).readUser(dbc, userName, password, remoteAddress);
        } catch (CmsDbEntryNotFoundException e) {
            // this indicates that the username / password combination does not exist
            // any other exception indicates database issues, these are not catched here

            // check if a user with this name exists at all
            CmsUser user = null;
            try {
                user = readUser(dbc, userName);
            } catch (CmsDataAccessException e2) {
                // apparently this user does not exist in the database
            }

            if (user != null) {
                if (dbc.currentUser().isGuestUser()) {
                    // add an invalid login attempt for this user to the storage
                    OpenCms.getLoginManager().addInvalidLogin(userName, remoteAddress);
                }
                OpenCms.getLoginManager().checkInvalidLogins(userName, remoteAddress);
                throw new CmsAuthentificationException(
                    org.opencms.security.Messages.get().container(
                        org.opencms.security.Messages.ERR_LOGIN_FAILED_2,
                        userName,
                        remoteAddress),
                    e);
            } else {
                String userOu = CmsOrganizationalUnit.getParentFqn(userName);
                if (userOu != null) {
                    String parentOu = CmsOrganizationalUnit.getParentFqn(userOu);
                    if (parentOu != null) {
                        // try a higher level ou
                        String uName = CmsOrganizationalUnit.getSimpleName(userName);
                        return loginUser(dbc, parentOu + uName, password, remoteAddress);
                    }
                }
                throw new CmsAuthentificationException(
                    org.opencms.security.Messages.get().container(
                        org.opencms.security.Messages.ERR_LOGIN_FAILED_NO_USER_2,
                        userName,
                        remoteAddress),
                    e);
            }
        }
        // check if the "enabled" flag is set for the user
        if (!newUser.isEnabled()) {
            // user is disabled, throw a securiy exception
            throw new CmsAuthentificationException(
                org.opencms.security.Messages.get().container(
                    org.opencms.security.Messages.ERR_LOGIN_FAILED_DISABLED_2,
                    userName,
                    remoteAddress));
        }

        if (dbc.currentUser().isGuestUser()) {
            // check if this account is temporarily disabled because of too many invalid login attempts
            // this will throw an exception if the test fails
            OpenCms.getLoginManager().checkInvalidLogins(userName, remoteAddress);
            // test successful, remove all previous invalid login attempts for this user from the storage
            OpenCms.getLoginManager().removeInvalidLogins(userName, remoteAddress);
        }

        if (!m_securityManager.hasRole(
            dbc,
            newUser,
            CmsRole.ADMINISTRATOR.forOrgUnit(dbc.getRequestContext().getOuFqn()))) {
            // new user is not Administrator, check if login is currently allowed
            OpenCms.getLoginManager().checkLoginAllowed();
        }
        m_monitor.clearUserCache(newUser);
        // set the last login time to the current time
        newUser.setLastlogin(System.currentTimeMillis());

        // write the changed user object back to the user driver
        Map<String, Object> additionalInfosForRepositories = OpenCms.getRepositoryManager().getAdditionalInfoForLogin(
            newUser.getName(),
            password);
        boolean requiresAddInfoUpdate = false;

        // check for changes
        for (Entry<String, Object> entry : additionalInfosForRepositories.entrySet()) {
            Object value = entry.getValue();
            Object current = newUser.getAdditionalInfo(entry.getKey());
            if (((value == null) && (current != null)) || ((value != null) && !value.equals(current))) {
                requiresAddInfoUpdate = true;
                break;
            }
        }
        if (requiresAddInfoUpdate) {
            newUser.getAdditionalInfo().putAll(additionalInfosForRepositories);
        }
        String lastPasswordChange = (String)newUser.getAdditionalInfo(
            CmsUserSettings.ADDITIONAL_INFO_LAST_PASSWORD_CHANGE);
        if (lastPasswordChange == null) {
            requiresAddInfoUpdate = true;
            newUser.getAdditionalInfo().put(
                CmsUserSettings.ADDITIONAL_INFO_LAST_PASSWORD_CHANGE,
                "" + System.currentTimeMillis());
        }
        if (!requiresAddInfoUpdate) {
            dbc.setAttribute(ATTRIBUTE_LOGIN, newUser.getName());
        }
        getUserDriver(dbc).writeUser(dbc, newUser);
        int changes = CmsUser.FLAG_LAST_LOGIN;

        // check if we need to update the password
        if (!OpenCms.getPasswordHandler().checkPassword(password, newUser.getPassword(), false)
            && OpenCms.getPasswordHandler().checkPassword(password, newUser.getPassword(), true)) {
            // the password does not check with the current hash algorithm but with the fall back, update the password
            getUserDriver(dbc).writePassword(dbc, userName, password, password);
            changes = changes | CmsUser.FLAG_CORE_DATA;
        }

        // update cache
        m_monitor.cacheUser(newUser);

        // invalidate all user dependent caches
        m_monitor.flushCache(
            CmsMemoryMonitor.CacheType.ACL,
            CmsMemoryMonitor.CacheType.GROUP,
            CmsMemoryMonitor.CacheType.ORG_UNIT,
            CmsMemoryMonitor.CacheType.USERGROUPS,
            CmsMemoryMonitor.CacheType.USER_LIST,
            CmsMemoryMonitor.CacheType.PERMISSION,
            CmsMemoryMonitor.CacheType.RESOURCE_LIST);

        // fire user modified event
        Map<String, Object> eventData = new HashMap<String, Object>();
        eventData.put(I_CmsEventListener.KEY_USER_ID, newUser.getId().toString());
        eventData.put(I_CmsEventListener.KEY_USER_NAME, newUser.getName());
        eventData.put(I_CmsEventListener.KEY_USER_ACTION, I_CmsEventListener.VALUE_USER_MODIFIED_ACTION_WRITE_USER);
        eventData.put(I_CmsEventListener.KEY_USER_CHANGES, Integer.valueOf(changes));
        OpenCms.fireCmsEvent(new CmsEvent(I_CmsEventListener.EVENT_USER_MODIFIED, eventData));

        // return the user object read from the driver
        return newUser.clone();
    }

    /**
     * Lookup and read the user or group with the given UUID.<p>
     *
     * @param dbc the current database context
     * @param principalId the UUID of the principal to lookup
     *
     * @return the principal (group or user) if found, otherwise <code>null</code>
     */
    public I_CmsPrincipal lookupPrincipal(CmsDbContext dbc, CmsUUID principalId) {

        try {
            CmsGroup group = getUserDriver(dbc).readGroup(dbc, principalId);
            if (group != null) {
                return group;
            }
        } catch (Exception e) {
            // ignore this exception
        }

        try {
            CmsUser user = readUser(dbc, principalId);
            if (user != null) {
                return user;
            }
        } catch (Exception e) {
            // ignore this exception
        }

        return null;
    }

    /**
     * Lookup and read the user or group with the given name.<p>
     *
     * @param dbc the current database context
     * @param principalName the name of the principal to lookup
     *
     * @return the principal (group or user) if found, otherwise <code>null</code>
     */
    public I_CmsPrincipal lookupPrincipal(CmsDbContext dbc, String principalName) {

        try {
            CmsGroup group = getUserDriver(dbc).readGroup(dbc, principalName);
            if (group != null) {
                return group;
            }
        } catch (Exception e) {
            // ignore this exception
        }

        try {
            CmsUser user = readUser(dbc, principalName);
            if (user != null) {
                return user;
            }
        } catch (Exception e) {
            // ignore this exception
        }

        return null;
    }

    /**
     * Mark the given resource as visited by the user.<p>
     *
     * @param dbc the database context
     * @param poolName the name of the database pool to use
     * @param resource the resource to mark as visited
     * @param user the user that visited the resource
     *
     * @throws CmsException if something goes wrong
     */
    public void markResourceAsVisitedBy(CmsDbContext dbc, String poolName, CmsResource resource, CmsUser user)
    throws CmsException {

        getSubscriptionDriver().markResourceAsVisitedBy(dbc, poolName, resource, user);
    }

    /**
     * Moves a resource.<p>
     *
     * You must ensure that the parent of the destination path is an absolute, valid and
     * existing VFS path. Relative paths from the source are not supported.<p>
     *
     * The moved resource will always be locked to the current user
     * after the move operation.<p>
     *
     * In case the target resource already exists, it will be overwritten with the
     * source resource if possible.<p>
     *
     * @param dbc the current database context
     * @param source the resource to move
     * @param destination the name of the move destination with complete path
     * @param internal if set nothing more than the path is modified
     *
     * @throws CmsException if something goes wrong
     *
     * @see CmsSecurityManager#moveResource(CmsRequestContext, CmsResource, String)
     */
    public void moveResource(CmsDbContext dbc, CmsResource source, String destination, boolean internal)
    throws CmsException {

        CmsFolder destinationFolder = readFolder(dbc, CmsResource.getParentFolder(destination), CmsResourceFilter.ALL);
        m_securityManager.checkPermissions(
            dbc,
            destinationFolder,
            CmsPermissionSet.ACCESS_WRITE,
            false,
            CmsResourceFilter.ALL);

        if (source.isFolder()) {
            m_monitor.flushCache(CmsMemoryMonitor.CacheType.HAS_ROLE, CmsMemoryMonitor.CacheType.ROLE_LIST);
        }
        getVfsDriver(dbc).moveResource(dbc, dbc.getRequestContext().getCurrentProject().getUuid(), source, destination);

        if (!internal) {
            CmsResourceState newState = CmsResource.STATE_CHANGED;
            if (source.getState().isNew()) {
                newState = CmsResource.STATE_NEW;
            } else if (source.getState().isDeleted()) {
                newState = CmsResource.STATE_DELETED;
            }
            source.setState(newState);
            // safe since this operation always uses the ids instead of the resource path
            getVfsDriver(dbc).writeResourceState(
                dbc,
                dbc.currentProject(),
                source,
                CmsDriverManager.UPDATE_STRUCTURE_STATE,
                false);
            // log it
            log(
                dbc,
                new CmsLogEntry(
                    dbc,
                    source.getStructureId(),
                    CmsLogEntryType.RESOURCE_MOVED,
                    new String[] {source.getRootPath(), destination}),
                false);
        }

        CmsResource destRes = readResource(dbc, destination, CmsResourceFilter.ALL);
        // move lock
        m_lockManager.moveResource(source.getRootPath(), destRes.getRootPath());

        // flush all relevant caches
        m_monitor.clearAccessControlListCache();
        m_monitor.flushCache(
            CmsMemoryMonitor.CacheType.PROPERTY,
            CmsMemoryMonitor.CacheType.PROPERTY_LIST,
            CmsMemoryMonitor.CacheType.PROJECT_RESOURCES);

        List<CmsResource> resources = new ArrayList<CmsResource>(4);
        // source
        resources.add(source);
        try {
            resources.add(readFolder(dbc, CmsResource.getParentFolder(source.getRootPath()), CmsResourceFilter.ALL));
        } catch (Exception e) {
            if (LOG.isDebugEnabled()) {
                LOG.debug(e);
            }
        }
        // destination
        resources.add(destRes);
        resources.add(destinationFolder);

        Map<String, Object> eventData = new HashMap<String, Object>();
        eventData.put(I_CmsEventListener.KEY_RESOURCES, resources);
        eventData.put(I_CmsEventListener.KEY_DBCONTEXT, dbc);

        // fire the events
        OpenCms.fireCmsEvent(new CmsEvent(I_CmsEventListener.EVENT_RESOURCE_MOVED, eventData));
    }

    /**
     * Moves a resource to the "lost and found" folder.<p>
     *
     * The method can also be used to check get the name of a resource
     * in the "lost and found" folder only without actually moving the
     * the resource. To do this, the <code>returnNameOnly</code> flag
     * must be set to <code>true</code>.<p>
     *
     * @param dbc the current database context
     * @param resource the resource to apply this operation to
     * @param returnNameOnly if <code>true</code>, only the name of the resource in the "lost and found"
     *        folder is returned, the move operation is not really performed
     *
     * @return the name of the resource inside the "lost and found" folder
     *
     * @throws CmsException if something goes wrong
     * @throws CmsIllegalArgumentException if the <code>resourcename</code> argument is null or of length 0
     *
     * @see CmsObject#moveToLostAndFound(String)
     * @see CmsObject#getLostAndFoundName(String)
     */
    public String moveToLostAndFound(CmsDbContext dbc, CmsResource resource, boolean returnNameOnly)
    throws CmsException, CmsIllegalArgumentException {

        String resourcename = dbc.removeSiteRoot(resource.getRootPath());

        String siteRoot = dbc.getRequestContext().getSiteRoot();
        dbc.getRequestContext().setSiteRoot("");
        String destination = CmsDriverManager.LOST_AND_FOUND_FOLDER + resourcename;
        // create the required folders if necessary
        try {
            // collect all folders...
            String folderPath = CmsResource.getParentFolder(destination);
            folderPath = folderPath.substring(1, folderPath.length() - 1); // cut out leading and trailing '/'
            Iterator<String> folders = CmsStringUtil.splitAsList(folderPath, '/').iterator();
            // ...now create them....
            folderPath = "/";
            while (folders.hasNext()) {
                folderPath += folders.next().toString() + "/";
                try {
                    readFolder(dbc, folderPath, CmsResourceFilter.IGNORE_EXPIRATION);
                } catch (Exception e1) {
                    if (returnNameOnly) {
                        // we can use the original name without risk, and we do not need to recreate the parent folders
                        break;
                    }
                    // the folder is not existing, so create it
                    createResource(
                        dbc,
                        folderPath,
                        CmsResourceTypeFolder.RESOURCE_TYPE_ID,
                        null,
                        new ArrayList<CmsProperty>());
                }
            }
            // check if this resource name does already exist
            // if so add a postfix to the name
            String des = destination;
            int postfix = 1;
            boolean found = true;
            while (found) {
                try {
                    // try to read the file.....
                    found = true;
                    readResource(dbc, des, CmsResourceFilter.ALL);
                    // ....it's there, so add a postfix and try again
                    String path = destination.substring(0, destination.lastIndexOf('/') + 1);
                    String filename = destination.substring(destination.lastIndexOf('/') + 1, destination.length());

                    des = path;

                    if (filename.lastIndexOf('.') > 0) {
                        des += filename.substring(0, filename.lastIndexOf('.'));
                    } else {
                        des += filename;
                    }
                    des += "_" + postfix;
                    if (filename.lastIndexOf('.') > 0) {
                        des += filename.substring(filename.lastIndexOf('.'), filename.length());
                    }
                    postfix++;
                } catch (CmsException e3) {
                    // the file does not exist, so we can use this filename
                    found = false;
                }
            }
            destination = des;

            if (!returnNameOnly) {
                // do not use the move semantic here! to prevent links pointing to the lost & found folder
                copyResource(dbc, resource, destination, CmsResource.COPY_AS_SIBLING);
                deleteResource(dbc, resource, CmsResource.DELETE_PRESERVE_SIBLINGS);
            }
        } catch (CmsException e2) {
            throw e2;
        } finally {
            // set the site root to the old value again
            dbc.getRequestContext().setSiteRoot(siteRoot);
        }
        return destination;
    }

    /**
     * Gets a new driver instance.<p>
     *
     * @param dbc the database context
     * @param configurationManager the configuration manager
     * @param driverName the driver name
     * @param successiveDrivers the list of successive drivers
     *
     * @return the driver object
     * @throws CmsInitException if the selected driver could not be initialized
     */
    public Object newDriverInstance(
        CmsDbContext dbc,
        CmsConfigurationManager configurationManager,
        String driverName,
        List<String> successiveDrivers)
    throws CmsInitException {

        Class<?> driverClass = null;
        I_CmsDriver driver = null;

        try {
            // try to get the class
            driverClass = Class.forName(driverName);
            if (CmsLog.INIT.isInfoEnabled()) {
                CmsLog.INIT.info(Messages.get().getBundle().key(Messages.INIT_DRIVER_START_1, driverName));
            }

            // try to create a instance
            driver = (I_CmsDriver)driverClass.newInstance();
            if (CmsLog.INIT.isInfoEnabled()) {
                CmsLog.INIT.info(Messages.get().getBundle().key(Messages.INIT_DRIVER_INITIALIZING_1, driverName));
            }

            // invoke the init-method of this access class
            driver.init(dbc, configurationManager, successiveDrivers, this);
            if (CmsLog.INIT.isInfoEnabled()) {
                CmsLog.INIT.info(Messages.get().getBundle().key(Messages.INIT_DRIVER_INIT_FINISHED_0));
            }

        } catch (Throwable t) {
            CmsMessageContainer message = Messages.get().container(
                Messages.ERR_ERROR_INITIALIZING_DRIVER_1,
                driverName);
            if (LOG.isErrorEnabled()) {
                LOG.error(message.key(), t);
            }
            throw new CmsInitException(message, t);
        }

        return driver;
    }

    /**
     * Method to create a new instance of a driver.<p>
     *
     * @param configuration the configurations from the propertyfile
     * @param driverName the class name of the driver
     * @param driverPoolUrl the pool url for the driver
     * @return an initialized instance of the driver
     * @throws CmsException if something goes wrong
     */
    public Object newDriverInstance(CmsParameterConfiguration configuration, String driverName, String driverPoolUrl)
    throws CmsException {

        Class<?>[] initParamClasses = {CmsParameterConfiguration.class, String.class, CmsDriverManager.class};
        Object[] initParams = {configuration, driverPoolUrl, this};

        Class<?> driverClass = null;
        Object driver = null;

        try {
            // try to get the class
            driverClass = Class.forName(driverName);
            if (CmsLog.INIT.isInfoEnabled()) {
                CmsLog.INIT.info(Messages.get().getBundle().key(Messages.INIT_DRIVER_START_1, driverName));
            }

            // try to create a instance
            driver = driverClass.newInstance();
            if (CmsLog.INIT.isInfoEnabled()) {
                CmsLog.INIT.info(Messages.get().getBundle().key(Messages.INIT_DRIVER_INITIALIZING_1, driverName));
            }

            // invoke the init-method of this access class
            driver.getClass().getMethod("init", initParamClasses).invoke(driver, initParams);
            if (CmsLog.INIT.isInfoEnabled()) {
                CmsLog.INIT.info(Messages.get().getBundle().key(Messages.INIT_DRIVER_INIT_FINISHED_1, driverPoolUrl));
            }

        } catch (Exception exc) {

            CmsMessageContainer message = Messages.get().container(Messages.ERR_INIT_DRIVER_MANAGER_1);
            if (LOG.isFatalEnabled()) {
                LOG.fatal(message.key(), exc);
            }
            throw new CmsDbException(message, exc);

        }

        return driver;
    }

    /**
     * Method to create a new instance of a pool.<p>
     *
     * @param configuration the configurations from the propertyfile
     * @param poolName the configuration name of the pool
     *
     * @throws CmsInitException if the pools could not be initialized
     */
    public void newPoolInstance(CmsParameterConfiguration configuration, String poolName) throws CmsInitException {

        CmsDbPoolV11 pool;

        try {
            pool = new CmsDbPoolV11(configuration, poolName);
        } catch (Exception e) {

            CmsMessageContainer message = Messages.get().container(Messages.ERR_INIT_CONN_POOL_1, poolName);
            if (LOG.isErrorEnabled()) {
                LOG.error(message.key(), e);
            }
            throw new CmsInitException(message, e);
        }
        addPool(pool);
    }

    /**
     * Publishes the given publish job.<p>
     *
     * @param cms the cms context
     * @param dbc the db context
     * @param publishList the list of resources to publish
     * @param report the report to write to
     *
     * @throws CmsException if something goes wrong
     */
    public void publishJob(CmsObject cms, CmsDbContext dbc, CmsPublishList publishList, I_CmsReport report)
    throws CmsException {

        try {
            // check state and lock
            List<CmsResource> allResources = new ArrayList<CmsResource>(publishList.getFolderList());
            allResources.addAll(publishList.getDeletedFolderList());
            allResources.addAll(publishList.getFileList());
            Iterator<CmsResource> itResources = allResources.iterator();
            while (itResources.hasNext()) {
                CmsResource resource = itResources.next();
                try {
                    resource = readResource(dbc, resource.getStructureId(), CmsResourceFilter.ALL);
                } catch (CmsVfsResourceNotFoundException e) {
                    continue;
                }
                if (resource.getState().isUnchanged()) {
                    // remove files that were published by a concurrent job
                    if (LOG.isDebugEnabled()) {
                        LOG.debug(
                            Messages.get().getBundle().key(
                                Messages.RPT_PUBLISH_REMOVED_RESOURCE_1,
                                dbc.removeSiteRoot(resource.getRootPath())));
                    }
                    publishList.remove(resource);
                    unlockResource(dbc, resource, true, true);
                    continue;
                }
                CmsLock lock = m_lockManager.getLock(dbc, resource, false);
                if (!lock.getSystemLock().isPublish()) {
                    // remove files that are not locked for publishing
                    if (LOG.isDebugEnabled()) {
                        LOG.debug(
                            Messages.get().getBundle().key(
                                Messages.RPT_PUBLISH_REMOVED_RESOURCE_1,
                                dbc.removeSiteRoot(resource.getRootPath())));
                    }
                    publishList.remove(resource);
                    continue;
                }
            }

            CmsProject onlineProject = readProject(dbc, CmsProject.ONLINE_PROJECT_ID);

            // clear the cache
            m_monitor.clearCache();

            int publishTag = getNextPublishTag(dbc);
            getProjectDriver(dbc).publishProject(dbc, report, onlineProject, publishList, publishTag);

            // iterate the initialized module action instances
            Iterator<String> i = OpenCms.getModuleManager().getModuleNames().iterator();
            while (i.hasNext()) {
                CmsModule module = OpenCms.getModuleManager().getModule(i.next());
                if ((module != null) && (module.getActionInstance() != null)) {
                    module.getActionInstance().publishProject(cms, publishList, publishTag, report);
                }
            }

            boolean temporaryProject = (cms.getRequestContext().getCurrentProject().getType() == CmsProject.PROJECT_TYPE_TEMPORARY);
            // the project was stored in the history tables for history
            // it will be deleted if the project_flag is PROJECT_TYPE_TEMPORARY
            if ((temporaryProject) && (!publishList.isDirectPublish())) {
                try {
                    getProjectDriver(dbc).deleteProject(dbc, dbc.currentProject());
                } catch (CmsException e) {
                    LOG.error(
                        Messages.get().getBundle().key(
                            Messages.LOG_DELETE_TEMP_PROJECT_FAILED_1,
                            cms.getRequestContext().getCurrentProject().getName()));
                }
                // if project was temporary set context to online project
                cms.getRequestContext().setCurrentProject(onlineProject);
            }
        } finally {
            // clear the cache again
            m_monitor.clearCache();
        }
    }

    /**
     * Publishes the resources of a specified publish list.<p>
     *
     * @param cms the current request context
     * @param dbc the current database context
     * @param publishList a publish list
     * @param report an instance of <code>{@link I_CmsReport}</code> to print messages
     *
     * @throws CmsException if something goes wrong
     *
     * @see #fillPublishList(CmsDbContext, CmsPublishList)
     */
    public synchronized void publishProject(
        CmsObject cms,
        CmsDbContext dbc,
        CmsPublishList publishList,
        I_CmsReport report)
    throws CmsException {

        // check the parent folders
        checkParentFolders(dbc, publishList);
        ensureSubResourcesOfMovedFoldersPublished(cms, dbc, publishList);
        OpenCms.getPublishManager().getPublishListVerifier().checkPublishList(publishList);

        try {
            // fire an event that a project is to be published
            Map<String, Object> eventData = new HashMap<String, Object>();
            eventData.put(I_CmsEventListener.KEY_REPORT, report);
            eventData.put(I_CmsEventListener.KEY_PUBLISHLIST, publishList);
            eventData.put(I_CmsEventListener.KEY_PROJECTID, dbc.currentProject().getUuid());
            eventData.put(I_CmsEventListener.KEY_DBCONTEXT, dbc);
            CmsEvent beforePublishEvent = new CmsEvent(I_CmsEventListener.EVENT_BEFORE_PUBLISH_PROJECT, eventData);
            OpenCms.fireCmsEvent(beforePublishEvent);
        } catch (Throwable t) {
            if (report != null) {
                report.addError(t);
                report.println(t);
            }
            if (LOG.isErrorEnabled()) {
                LOG.error(t.getLocalizedMessage(), t);
            }
        }

        // lock all resources with the special publish lock
        Iterator<CmsResource> itResources = new ArrayList<CmsResource>(publishList.getAllResources()).iterator();
        while (itResources.hasNext()) {
            CmsResource resource = itResources.next();
            CmsLock lock = m_lockManager.getLock(dbc, resource, false);
            if (lock.getSystemLock().isUnlocked() && lock.isLockableBy(dbc.currentUser())) {
                if (getLock(dbc, resource).getEditionLock().isNullLock()) {
                    lockResource(dbc, resource, CmsLockType.PUBLISH);
                } else {
                    changeLock(dbc, resource, CmsLockType.PUBLISH);
                }
            } else if (lock.getSystemLock().isPublish()) {
                if (LOG.isWarnEnabled()) {
                    LOG.warn(
                        Messages.get().getBundle().key(
                            Messages.RPT_PUBLISH_REMOVED_RESOURCE_1,
                            dbc.removeSiteRoot(resource.getRootPath())));
                }
                // remove files that are already waiting to be published
                publishList.remove(resource);
                continue;
            } else {
                // this is needed to fix TestPublishIsssues#testPublishScenarioE
                changeLock(dbc, resource, CmsLockType.PUBLISH);
            }
            // now re-check the lock state
            lock = m_lockManager.getLock(dbc, resource, false);
            if (!lock.getSystemLock().isPublish()) {
                if (report != null) {
                    report.println(
                        Messages.get().container(
                            Messages.RPT_PUBLISH_REMOVED_RESOURCE_1,
                            dbc.removeSiteRoot(resource.getRootPath())),
                        I_CmsReport.FORMAT_WARNING);
                }
                if (LOG.isWarnEnabled()) {
                    LOG.warn(
                        Messages.get().getBundle().key(
                            Messages.RPT_PUBLISH_REMOVED_RESOURCE_1,
                            dbc.removeSiteRoot(resource.getRootPath())));
                }
                // remove files that could not be locked
                publishList.remove(resource);
            }
        }

        // enqueue the publish job
        CmsException enqueueException = null;
        try {
            m_publishEngine.enqueuePublishJob(cms, publishList, report);
        } catch (CmsException exc) {
            enqueueException = exc;
        }

        // if an exception was raised, remove the publish locks
        // and throw the exception again
        if (enqueueException != null) {
            itResources = publishList.getAllResources().iterator();
            while (itResources.hasNext()) {
                CmsResource resource = itResources.next();
                CmsLock lock = m_lockManager.getLock(dbc, resource, false);
                if (lock.getSystemLock().isPublish()
                    && lock.getSystemLock().isOwnedInProjectBy(
                        cms.getRequestContext().getCurrentUser(),
                        cms.getRequestContext().getCurrentProject())) {
                    unlockResource(dbc, resource, true, true);
                }
            }

            throw enqueueException;
        }
    }

    /**
     * Transfers the new URL name mappings (if any) for a given resource to the online project.<p>
     *
     * @param dbc the current database context
     * @param res the resource whose new URL name mappings should be transferred to the online project
     *
     * @throws CmsDataAccessException if something goes wrong
     */
    public void publishUrlNameMapping(CmsDbContext dbc, CmsResource res) throws CmsDataAccessException {

        I_CmsVfsDriver vfsDriver = getVfsDriver(dbc);

        if (res.getState().isDeleted()) {
            // remove both offline and online mappings
            CmsUrlNameMappingFilter idFilter = CmsUrlNameMappingFilter.ALL.filterStructureId(res.getStructureId());
            vfsDriver.deleteUrlNameMappingEntries(dbc, true, idFilter);
            vfsDriver.deleteUrlNameMappingEntries(dbc, false, idFilter);
        } else {
            // copy the new entries to the online table
            List<CmsUrlNameMappingEntry> entries = vfsDriver.readUrlNameMappingEntries(
                dbc,
                false,
                CmsUrlNameMappingFilter.ALL.filterStructureId(res.getStructureId()).filterStates(
                    CmsUrlNameMappingEntry.MAPPING_STATUS_NEW,
                    CmsUrlNameMappingEntry.MAPPING_STATUS_REPLACE_ON_PUBLISH));

            boolean isReplaceOnPublish = false;
            for (CmsUrlNameMappingEntry entry : entries) {
                isReplaceOnPublish |= entry.getState() == CmsUrlNameMappingEntry.MAPPING_STATUS_REPLACE_ON_PUBLISH;
            }

            if (!entries.isEmpty()) {

                long now = System.currentTimeMillis();
                if (isReplaceOnPublish) {
                    vfsDriver.deleteUrlNameMappingEntries(
                        dbc,
                        true,
                        CmsUrlNameMappingFilter.ALL.filterStructureId(res.getStructureId()));
                    vfsDriver.deleteUrlNameMappingEntries(
                        dbc,
                        false,
                        CmsUrlNameMappingFilter.ALL.filterStructureId(res.getStructureId()));
                }

                for (CmsUrlNameMappingEntry entry : entries) {
                    CmsUrlNameMappingFilter nameFilter = CmsUrlNameMappingFilter.ALL.filterName(entry.getName());
                    if (!isReplaceOnPublish) { // we already handled the other case above
                        vfsDriver.deleteUrlNameMappingEntries(dbc, true, nameFilter);
                        vfsDriver.deleteUrlNameMappingEntries(dbc, false, nameFilter);
                    }
                }
                for (CmsUrlNameMappingEntry entry : entries) {
                    CmsUrlNameMappingEntry newEntry = new CmsUrlNameMappingEntry(
                        entry.getName(),
                        entry.getStructureId(),
                        entry.getState() == CmsUrlNameMappingEntry.MAPPING_STATUS_NEW
                        ? CmsUrlNameMappingEntry.MAPPING_STATUS_PUBLISHED
                        : CmsUrlNameMappingEntry.MAPPING_STATUS_REPLACE_ON_PUBLISH_PUBLISHED,
                        now,
                        entry.getLocale());
                    vfsDriver.addUrlNameMappingEntry(dbc, true, newEntry);
                    vfsDriver.addUrlNameMappingEntry(dbc, false, newEntry);
                }
            }
        }
    }

    /**
     * Reads an access control entry from the cms.<p>
     *
     * The access control entries of a resource are readable by everyone.
     *
     * @param dbc the current database context
     * @param resource the resource
     * @param principal the id of a group or a user any other entity
     * @return an access control entry that defines the permissions of the entity for the given resource
     * @throws CmsException if something goes wrong
     */
    public CmsAccessControlEntry readAccessControlEntry(CmsDbContext dbc, CmsResource resource, CmsUUID principal)
    throws CmsException {

        return getUserDriver(
            dbc).readAccessControlEntry(dbc, dbc.currentProject(), resource.getResourceId(), principal);
    }

    /**
     * Finds the alias with a given path.<p>
     *
     * If no alias is found, null is returned.<p>
     *
     * @param dbc the current database context
     * @param project the current project
     * @param siteRoot the site root
     * @param path the path of the alias
     *
     * @return the alias with the given path
     *
     * @throws CmsException if something goes wrong
     */

    public CmsAlias readAliasByPath(CmsDbContext dbc, CmsProject project, String siteRoot, String path)
    throws CmsException {

        List<CmsAlias> aliases = getVfsDriver(dbc).readAliases(dbc, project, new CmsAliasFilter(siteRoot, path, null));
        if (aliases.isEmpty()) {
            return null;
        } else {
            return aliases.get(0);
        }
    }

    /**
     * Reads the aliases for a given site root.<p>
     *
     * @param dbc the current database context
     * @param currentProject the current project
     * @param siteRoot the site root
     *
     * @return the list of aliases for the given site root
     *
     * @throws CmsException if something goes wrong
     */
    public List<CmsAlias> readAliasesBySite(CmsDbContext dbc, CmsProject currentProject, String siteRoot)
    throws CmsException {

        return getVfsDriver(dbc).readAliases(dbc, currentProject, new CmsAliasFilter(siteRoot, null, null));
    }

    /**
     * Reads the aliases which point to a given structure id.<p>
     *
     * @param dbc the current database context
     * @param project the current project
     * @param structureId the structure id for which we want to read the aliases
     *
     * @return the list of aliases pointing to the structure id
     * @throws CmsException if something goes wrong
     */
    public List<CmsAlias> readAliasesByStructureId(CmsDbContext dbc, CmsProject project, CmsUUID structureId)
    throws CmsException {

        return getVfsDriver(dbc).readAliases(dbc, project, new CmsAliasFilter(null, null, structureId));
    }

    /**
     * Reads all versions of the given resource.<br>
     *
     * This method returns a list with the history of the given resource, i.e.
     * the historical resource entries, independent of the project they were attached to.<br>
     *
     * The reading excludes the file content.<p>
     *
     * @param dbc the current database context
     * @param resource the resource to read the history for
     *
     * @return a list of file headers, as <code>{@link I_CmsHistoryResource}</code> objects
     *
     * @throws CmsException if something goes wrong
     */
    public List<I_CmsHistoryResource> readAllAvailableVersions(CmsDbContext dbc, CmsResource resource)
    throws CmsException {

        // read the historical resources
        List<I_CmsHistoryResource> versions = getHistoryDriver(dbc).readAllAvailableVersions(
            dbc,
            resource.getStructureId());
        if ((versions.size() > OpenCms.getSystemInfo().getHistoryVersions())
            && (OpenCms.getSystemInfo().getHistoryVersions() > -1)) {
            return versions.subList(0, OpenCms.getSystemInfo().getHistoryVersions());
        }
        return versions;
    }

    /**
     * Reads all property definitions for the given mapping type.<p>
     *
     * @param dbc the current database context
     *
     * @return a list with the <code>{@link CmsPropertyDefinition}</code> objects (may be empty)
     *
     * @throws CmsException if something goes wrong
     */
    public List<CmsPropertyDefinition> readAllPropertyDefinitions(CmsDbContext dbc) throws CmsException {

        List<CmsPropertyDefinition> result = getVfsDriver(dbc).readPropertyDefinitions(
            dbc,
            dbc.currentProject().getUuid());
        Collections.sort(result);
        return result;
    }

    /**
     * Returns all resources subscribed by the given user or group.<p>
     *
     * @param dbc the database context
     * @param poolName the name of the database pool to use
     * @param principal the principal to read the subscribed resources
     *
     * @return all resources subscribed by the given user or group
     *
     * @throws CmsException if something goes wrong
     */
    public List<CmsResource> readAllSubscribedResources(CmsDbContext dbc, String poolName, CmsPrincipal principal)
    throws CmsException {

        List<CmsResource> result = getSubscriptionDriver().readAllSubscribedResources(dbc, poolName, principal);
        result = filterPermissions(dbc, result, CmsResourceFilter.DEFAULT);
        return result;
    }

    /**
     * Selects the best url name for a given resource and locale.<p>
     *
     * @param dbc the database context
     * @param id the resource's structure id
     * @param locale the requested locale
     * @param defaultLocales the default locales to use if the locale isn't available
     *
     * @return the URL name which was found
     *
     * @throws CmsDataAccessException if the database operation failed
     */
    public String readBestUrlName(CmsDbContext dbc, CmsUUID id, Locale locale, List<Locale> defaultLocales)
    throws CmsDataAccessException {

        List<CmsUrlNameMappingEntry> entries = getVfsDriver(dbc).readUrlNameMappingEntries(
            dbc,
            dbc.currentProject().isOnlineProject(),
            CmsUrlNameMappingFilter.ALL.filterStructureId(id));
        if (entries.isEmpty()) {
            return null;
        }

        ArrayListMultimap<String, CmsUrlNameMappingEntry> entriesByLocale = ArrayListMultimap.create();
        for (CmsUrlNameMappingEntry entry : entries) {
            entriesByLocale.put(entry.getLocale(), entry);
        }
        List<CmsUrlNameMappingEntry> lastEntries = new ArrayList<CmsUrlNameMappingEntry>();
        Comparator<CmsUrlNameMappingEntry> dateChangedComparator = new UrlNameMappingComparator();
        for (String localeKey : entriesByLocale.keySet()) {
            // for each locale select the latest mapping entry
            CmsUrlNameMappingEntry latestEntryForLocale = Collections.max(
                entriesByLocale.get(localeKey),
                dateChangedComparator);
            lastEntries.add(latestEntryForLocale);
        }
        CmsLocaleManager localeManager = OpenCms.getLocaleManager();
        List<Locale> availableLocales = new ArrayList<Locale>();
        for (CmsUrlNameMappingEntry entry : lastEntries) {
            availableLocales.add(CmsLocaleManager.getLocale(entry.getLocale()));
        }
        Locale bestLocale = localeManager.getBestMatchingLocale(locale, defaultLocales, availableLocales);
        String bestLocaleStr = bestLocale.toString();
        for (CmsUrlNameMappingEntry entry : lastEntries) {
            if (entry.getLocale().equals(bestLocaleStr)) {
                return entry.getName();
            }
        }
        return null;
    }

    /**
     * Returns the child resources of a resource, that is the resources
     * contained in a folder.<p>
     *
     * With the parameters <code>getFolders</code> and <code>getFiles</code>
     * you can control what type of resources you want in the result list:
     * files, folders, or both.<p>
     *
     * This method is mainly used by the workplace explorer.<p>
     *
     * @param dbc the current database context
     * @param resource the resource to return the child resources for
     * @param filter the resource filter to use
     * @param getFolders if true the child folders are included in the result
     * @param getFiles if true the child files are included in the result
     * @param checkPermissions if the resources should be filtered with the current user permissions
     *
     * @return a list of all child resources
     *
     * @throws CmsException if something goes wrong
     */
    public List<CmsResource> readChildResources(
        CmsDbContext dbc,
        CmsResource resource,
        CmsResourceFilter filter,
        boolean getFolders,
        boolean getFiles,
        boolean checkPermissions)
    throws CmsException {

        String cacheKey = null;
        List<CmsResource> resourceList = null;
        if (m_monitor.isEnabled(CmsMemoryMonitor.CacheType.RESOURCE_LIST)) { // check this here to skip the complex cache key generation
            String time = "";
            if (checkPermissions) {
                // ensure correct caching if site time offset is set
                if ((dbc.getRequestContext() != null)
                    && (OpenCms.getSiteManager().getSiteForSiteRoot(dbc.getRequestContext().getSiteRoot()) != null)) {
                    time += OpenCms.getSiteManager().getSiteForSiteRoot(
                        dbc.getRequestContext().getSiteRoot()).getSiteMatcher().getTimeOffset();
                }
            }
            // try to get the sub resources from the cache
            cacheKey = getCacheKey(
                new String[] {
                    dbc.currentUser().getName(),
                    getFolders
                    ? (getFiles ? CmsCacheKey.CACHE_KEY_SUBALL : CmsCacheKey.CACHE_KEY_SUBFOLDERS)
                    : CmsCacheKey.CACHE_KEY_SUBFILES,
                    checkPermissions ? "+" + time : "-",
                    filter.getCacheId(),
                    resource.getRootPath()},
                dbc);

            resourceList = m_monitor.getCachedResourceList(cacheKey);
        }
        if ((resourceList == null) || !dbc.getProjectId().isNullUUID()) {
            // read the result form the database
            resourceList = getVfsDriver(
                dbc).readChildResources(dbc, dbc.currentProject(), resource, getFolders, getFiles);

            if (checkPermissions) {
                // apply the permission filter
                resourceList = filterPermissions(dbc, resourceList, filter);
            }
            // cache the sub resources
            if (dbc.getProjectId().isNullUUID()) {
                m_monitor.cacheResourceList(cacheKey, resourceList);
            }
        }

        // we must always apply the result filter and update the context dates
        return updateContextDates(dbc, resourceList, filter);
    }

    /**
     * Returns the default file for the given folder.<p>
     *
     * If the given resource is a file, then this file is returned.<p>
     *
     * Otherwise, in case of a folder:<br>
     * <ol>
     *   <li>the {@link CmsPropertyDefinition#PROPERTY_DEFAULT_FILE} is checked, and
     *   <li>if still no file could be found, the configured default files in the
     *       <code>opencms-vfs.xml</code> configuration are iterated until a match is
     *       found, and
     *   <li>if still no file could be found, <code>null</code> is retuned
     * </ol>
     *
     * @param dbc the database context
     * @param resource the folder to get the default file for
     * @param resourceFilter the resource filter
     *
     * @return the default file for the given folder
     */
    public CmsResource readDefaultFile(CmsDbContext dbc, CmsResource resource, CmsResourceFilter resourceFilter) {

        // resource exists, lets check if we have a file or a folder
        if (resource.isFolder()) {
            // the resource is a folder, check if PROPERTY_DEFAULT_FILE is set on folder
            try {
                String defaultFileName = readPropertyObject(
                    dbc,
                    resource,
                    CmsPropertyDefinition.PROPERTY_DEFAULT_FILE,
                    false).getValue();
                // check if the default file property does not match the navigation level folder marker value
                if ((defaultFileName != null) && !CmsJspNavBuilder.NAVIGATION_LEVEL_FOLDER.equals(defaultFileName)) {
                    // property was set, so look up this file first
                    String folderName = CmsResource.getFolderPath(resource.getRootPath());
                    resource = readResource(dbc, folderName + defaultFileName, resourceFilter.addRequireFile());
                }
            } catch (CmsException e) {
                // ignore all other exceptions and continue the lookup process
                if (LOG.isDebugEnabled()) {
                    LOG.debug(e.getLocalizedMessage(), e);
                }
            }
            if (resource.isFolder()) {
                String folderName = CmsResource.getFolderPath(resource.getRootPath());
                // resource is (still) a folder, check default files specified in configuration
                Iterator<String> it = OpenCms.getDefaultFiles().iterator();
                while (it.hasNext()) {
                    String tmpResourceName = folderName + it.next();
                    try {
                        resource = readResource(dbc, tmpResourceName, resourceFilter.addRequireFile());
                        // no exception? So we have found the default file
                        // stop looking for default files
                        break;
                    } catch (CmsException e) {
                        // ignore all other exceptions and continue the lookup process
                        if (LOG.isDebugEnabled()) {
                            LOG.debug(e.getLocalizedMessage(), e);
                        }
                    }
                }
            }
        }
        if (resource.isFolder()) {
            // we only want files as a result for further processing
            resource = null;
        }
        return resource;
    }

    /**
     * Reads all deleted (historical) resources below the given path,
     * including the full tree below the path, if required.<p>
     *
     * @param dbc the current db context
     * @param resource the parent resource to read the resources from
     * @param readTree <code>true</code> to read all subresources
     * @param isVfsManager <code>true</code> if the current user has the vfs manager role
     *
     * @return a list of <code>{@link I_CmsHistoryResource}</code> objects
     *
     * @throws CmsException if something goes wrong
     *
     * @see CmsObject#readResource(CmsUUID, int)
     * @see CmsObject#readResources(String, CmsResourceFilter, boolean)
     * @see CmsObject#readDeletedResources(String, boolean)
     */
    public List<I_CmsHistoryResource> readDeletedResources(
        CmsDbContext dbc,
        CmsResource resource,
        boolean readTree,
        boolean isVfsManager)
    throws CmsException {

        Set<I_CmsHistoryResource> result = new HashSet<I_CmsHistoryResource>();
        List<I_CmsHistoryResource> deletedResources;
        dbc.getRequestContext().setAttribute("ATTR_RESOURCE_NAME", resource.getRootPath());
        try {
            deletedResources = getHistoryDriver(dbc).readDeletedResources(
                dbc,
                resource.getStructureId(),
                isVfsManager ? null : dbc.currentUser().getId());
        } finally {
            dbc.getRequestContext().removeAttribute("ATTR_RESOURCE_NAME");
        }
        result.addAll(deletedResources);
        Set<I_CmsHistoryResource> newResult = new HashSet<I_CmsHistoryResource>(result.size());
        I_CmsVfsDriver vfsDriver = getVfsDriver(dbc);
        Iterator<I_CmsHistoryResource> it = result.iterator();
        while (it.hasNext()) {
            I_CmsHistoryResource histRes = it.next();
            // adjust the paths
            try {
                if (vfsDriver.validateStructureIdExists(
                    dbc,
                    dbc.currentProject().getUuid(),
                    histRes.getStructureId())) {
                    newResult.add(histRes);
                    continue;
                }
                // adjust the path in case of deleted files
                String resourcePath = histRes.getRootPath();
                String resName = CmsResource.getName(resourcePath);
                String path = CmsResource.getParentFolder(resourcePath);

                CmsUUID parentId = histRes.getParentId();
                try {
                    // first look for the path through the parent id
                    path = readResource(dbc, parentId, CmsResourceFilter.IGNORE_EXPIRATION).getRootPath();
                } catch (CmsDataAccessException e) {
                    // if the resource with the parent id is not found, try to get a new parent id with the path
                    try {
                        parentId = readResource(dbc, path, CmsResourceFilter.IGNORE_EXPIRATION).getStructureId();
                    } catch (CmsDataAccessException e1) {
                        // ignore, the parent folder has been completely deleted
                    }
                }
                resourcePath = path + resName;

                boolean isFolder = resourcePath.endsWith("/");
                if (isFolder) {
                    newResult.add(
                        new CmsHistoryFolder(
                            histRes.getPublishTag(),
                            histRes.getStructureId(),
                            histRes.getResourceId(),
                            resourcePath,
                            histRes.getTypeId(),
                            histRes.getFlags(),
                            histRes.getProjectLastModified(),
                            histRes.getState(),
                            histRes.getDateCreated(),
                            histRes.getUserCreated(),
                            histRes.getDateLastModified(),
                            histRes.getUserLastModified(),
                            histRes.getDateReleased(),
                            histRes.getDateExpired(),
                            histRes.getVersion(),
                            parentId,
                            histRes.getResourceVersion(),
                            histRes.getStructureVersion()));
                } else {
                    newResult.add(
                        new CmsHistoryFile(
                            histRes.getPublishTag(),
                            histRes.getStructureId(),
                            histRes.getResourceId(),
                            resourcePath,
                            histRes.getTypeId(),
                            histRes.getFlags(),
                            histRes.getProjectLastModified(),
                            histRes.getState(),
                            histRes.getDateCreated(),
                            histRes.getUserCreated(),
                            histRes.getDateLastModified(),
                            histRes.getUserLastModified(),
                            histRes.getDateReleased(),
                            histRes.getDateExpired(),
                            histRes.getLength(),
                            histRes.getDateContent(),
                            histRes.getVersion(),
                            parentId,
                            null,
                            histRes.getResourceVersion(),
                            histRes.getStructureVersion()));
                }
            } catch (CmsDataAccessException e) {
                // should never happen
                if (LOG.isErrorEnabled()) {
                    LOG.error(e.getLocalizedMessage(), e);
                }
            }
        }
        if (readTree) {
            Iterator<I_CmsHistoryResource> itDeleted = deletedResources.iterator();
            while (itDeleted.hasNext()) {
                I_CmsHistoryResource delResource = itDeleted.next();
                if (delResource.isFolder()) {
                    newResult.addAll(readDeletedResources(dbc, (CmsFolder)delResource, readTree, isVfsManager));
                }
            }
            try {
                readResource(dbc, resource.getStructureId(), CmsResourceFilter.ALL);
                // resource exists, so recurse
                Iterator<CmsResource> itResources = readResources(
                    dbc,
                    resource,
                    CmsResourceFilter.ALL.addRequireFolder(),
                    readTree).iterator();
                while (itResources.hasNext()) {
                    CmsResource subResource = itResources.next();
                    if (subResource.isFolder()) {
                        newResult.addAll(readDeletedResources(dbc, subResource, readTree, isVfsManager));
                    }
                }
            } catch (Exception e) {
                // resource does not exists
                if (LOG.isDebugEnabled()) {
                    LOG.debug(e.getLocalizedMessage(), e);
                }
            }
        }
        List<I_CmsHistoryResource> finalRes = new ArrayList<I_CmsHistoryResource>(newResult);
        Collections.sort(finalRes, I_CmsResource.COMPARE_ROOT_PATH);
        return finalRes;
    }

    /**
     * Reads a file resource (including it's binary content) from the VFS,
     * using the specified resource filter.<p>
     *
     * In case you do not need the file content,
     * use <code>{@link #readResource(CmsDbContext, String, CmsResourceFilter)}</code> instead.<p>
     *
     * The specified filter controls what kind of resources should be "found"
     * during the read operation. This will depend on the application. For example,
     * using <code>{@link CmsResourceFilter#DEFAULT}</code> will only return currently
     * "valid" resources, while using <code>{@link CmsResourceFilter#IGNORE_EXPIRATION}</code>
     * will ignore the date release / date expired information of the resource.<p>
     *
     * @param dbc the current database context
     * @param resource the base file resource (without content)
     * @return the file read from the VFS
     * @throws CmsException if operation was not successful
     */
    public CmsFile readFile(CmsDbContext dbc, CmsResource resource) throws CmsException {

        if (resource.isFolder()) {
            throw new CmsVfsResourceNotFoundException(
                Messages.get().container(
                    Messages.ERR_ACCESS_FOLDER_AS_FILE_1,
                    dbc.removeSiteRoot(resource.getRootPath())));
        }

        CmsUUID projectId = dbc.currentProject().getUuid();
        CmsFile file = null;
        if (resource instanceof I_CmsHistoryResource) {
            file = new CmsHistoryFile((I_CmsHistoryResource)resource);
            file.setContents(
                getHistoryDriver(dbc).readContent(
                    dbc,
                    resource.getResourceId(),
                    ((I_CmsHistoryResource)resource).getPublishTag()));
        } else {
            file = new CmsFile(resource);
            file.setContents(getVfsDriver(dbc).readContent(dbc, projectId, resource.getResourceId()));
        }
        return file;
    }

    /**
     * Reads a folder from the VFS,
     * using the specified resource filter.<p>
     *
     * @param dbc the current database context
     * @param resourcename the name of the folder to read (full path)
     * @param filter the resource filter to use while reading
     *
     * @return the folder that was read
     *
     * @throws CmsDataAccessException if something goes wrong
     *
     * @see #readResource(CmsDbContext, String, CmsResourceFilter)
     * @see CmsObject#readFolder(String)
     * @see CmsObject#readFolder(String, CmsResourceFilter)
     */
    public CmsFolder readFolder(CmsDbContext dbc, String resourcename, CmsResourceFilter filter)
    throws CmsDataAccessException {

        CmsResource resource = readResource(dbc, resourcename, filter);

        return convertResourceToFolder(resource);
    }

    /**
     * Reads the group of a project.<p>
     *
     * @param dbc the current database context
     * @param project the project to read from
     *
     * @return the group of a resource
     */
    public CmsGroup readGroup(CmsDbContext dbc, CmsProject project) {

        try {
            return readGroup(dbc, project.getGroupId());
        } catch (CmsException exc) {
            return new CmsGroup(
                CmsUUID.getNullUUID(),
                CmsUUID.getNullUUID(),
                project.getGroupId() + "",
                "deleted group",
                0);
        }
    }

    /**
     * Reads a group based on its id.<p>
     *
     * @param dbc the current database context
     * @param groupId the id of the group that is to be read
     *
     * @return the requested group
     *
     * @throws CmsException if operation was not successful
     */
    public CmsGroup readGroup(CmsDbContext dbc, CmsUUID groupId) throws CmsException {

        CmsGroup group = null;
        // try to read group from cache
        group = m_monitor.getCachedGroup(groupId.toString());
        if (group == null) {
            group = getUserDriver(dbc).readGroup(dbc, groupId);
            m_monitor.cacheGroup(group);
        }
        return group;
    }

    /**
     * Reads a group based on its name.<p>
     *
     * @param dbc the current database context
     * @param groupname the name of the group that is to be read
     *
     * @return the requested group
     *
     * @throws CmsDataAccessException if operation was not successful
     */
    public CmsGroup readGroup(CmsDbContext dbc, String groupname) throws CmsDataAccessException {

        CmsGroup group = null;
        // try to read group from cache
        group = m_monitor.getCachedGroup(groupname);
        if (group == null) {
            group = getUserDriver(dbc).readGroup(dbc, groupname);
            m_monitor.cacheGroup(group);
        }
        return group;
    }

    /**
     * Reads a principal (an user or group) from the historical archive based on its ID.<p>
     *
     * @param dbc the current database context
     * @param principalId the id of the principal to read
     *
     * @return the historical principal entry with the given id
     *
     * @throws CmsException if something goes wrong, ie. {@link CmsDbEntryNotFoundException}
     *
     * @see CmsObject#readUser(CmsUUID)
     * @see CmsObject#readGroup(CmsUUID)
     * @see CmsObject#readHistoryPrincipal(CmsUUID)
     */
    public CmsHistoryPrincipal readHistoricalPrincipal(CmsDbContext dbc, CmsUUID principalId) throws CmsException {

        return getHistoryDriver(dbc).readPrincipal(dbc, principalId);
    }

    /**
     * Returns the latest historical project entry with the given id.<p>
     *
     * @param dbc the current database context
     * @param projectId the project id
     *
     * @return the requested historical project entry
     *
     * @throws CmsException if something goes wrong
     */
    public CmsHistoryProject readHistoryProject(CmsDbContext dbc, CmsUUID projectId) throws CmsException {

        return getHistoryDriver(dbc).readProject(dbc, projectId);
    }

    /**
     * Returns a historical project entry.<p>
     *
     * @param dbc the current database context
     * @param publishTag the publish tag of the project
     *
     * @return the requested historical project entry
     *
     * @throws CmsException if something goes wrong
     */
    public CmsHistoryProject readHistoryProject(CmsDbContext dbc, int publishTag) throws CmsException {

        return getHistoryDriver(dbc).readProject(dbc, publishTag);
    }

    /**
     * Reads the list of all <code>{@link CmsProperty}</code> objects that belongs to the given historical resource.<p>
     *
     * @param dbc the current database context
     * @param historyResource the historical resource to read the properties for
     *
     * @return the list of <code>{@link CmsProperty}</code> objects
     *
     * @throws CmsException if something goes wrong
     */
    public List<CmsProperty> readHistoryPropertyObjects(CmsDbContext dbc, I_CmsHistoryResource historyResource)
    throws CmsException {

        return getHistoryDriver(dbc).readProperties(dbc, historyResource);
    }

    /**
     * Reads the structure id which is mapped to a given URL name.<p>
     *
     * @param dbc the current database context
     * @param name the name for which the mapped structure id should be looked up
     *
     * @return the structure id which is mapped to the given name, or null if there is no such id
     *
     * @throws CmsDataAccessException if something goes wrong
     */
    public CmsUUID readIdForUrlName(CmsDbContext dbc, String name) throws CmsDataAccessException {

        List<CmsUrlNameMappingEntry> entries = getVfsDriver(dbc).readUrlNameMappingEntries(
            dbc,
            dbc.currentProject().isOnlineProject(),
            CmsUrlNameMappingFilter.ALL.filterName(name));
        if (entries.isEmpty()) {
            return null;
        }
        return entries.get(0).getStructureId();
    }

    /**
     * Reads the locks that were saved to the database in the previous run of OpenCms.<p>
     *
     * @param dbc the current database context
     *
     * @throws CmsException if something goes wrong
     */
    public void readLocks(CmsDbContext dbc) throws CmsException {

        m_lockManager.readLocks(dbc);
    }

    /**
     * Reads the manager group of a project.<p>
     *
     * @param dbc the current database context
     * @param project the project to read from
     *
     * @return the group of a resource
     */
    public CmsGroup readManagerGroup(CmsDbContext dbc, CmsProject project) {

        try {
            return readGroup(dbc, project.getManagerGroupId());
        } catch (CmsException exc) {
            // the group does not exist any more - return a dummy-group
            return new CmsGroup(
                CmsUUID.getNullUUID(),
                CmsUUID.getNullUUID(),
                project.getManagerGroupId() + "",
                "deleted group",
                0);
        }
    }

    /**
     * Reads the URL name which has been most recently mapped to the given structure id, or null
     * if no URL name is mapped to the id.<p>
     *
     * @param dbc the current database context
     * @param id a structure id
     * @return the name which has been most recently mapped to the given structure id
     *
     * @throws CmsDataAccessException if something goes wrong
     */
    public String readNewestUrlNameForId(CmsDbContext dbc, CmsUUID id) throws CmsDataAccessException {

        List<CmsUrlNameMappingEntry> entries = getVfsDriver(dbc).readUrlNameMappingEntries(
            dbc,
            dbc.currentProject().isOnlineProject(),
            CmsUrlNameMappingFilter.ALL.filterStructureId(id));
        if (entries.isEmpty()) {
            return null;
        }

        Collections.sort(entries, new UrlNameMappingComparator());
        CmsUrlNameMappingEntry lastEntry = entries.get(entries.size() - 1);
        return lastEntry.getName();
    }

    /**
     * Reads an organizational Unit based on its fully qualified name.<p>
     *
     * @param dbc the current db context
     * @param ouFqn the fully qualified name of the organizational Unit to be read
     *
     * @return the organizational Unit that with the provided fully qualified name
     *
     * @throws CmsException if something goes wrong
     */
    public CmsOrganizationalUnit readOrganizationalUnit(CmsDbContext dbc, String ouFqn) throws CmsException {

        CmsOrganizationalUnit organizationalUnit = null;
        // try to read organizational unit from cache
        organizationalUnit = m_monitor.getCachedOrgUnit(ouFqn);
        if (organizationalUnit == null) {
            organizationalUnit = getUserDriver(dbc).readOrganizationalUnit(dbc, ouFqn);
            m_monitor.cacheOrgUnit(organizationalUnit);
        }
        return organizationalUnit;
    }

    /**
     * Reads the owner of a project.<p>
     *
     * @param dbc the current database context
     * @param project the project to get the owner from
     *
     * @return the owner of a resource
     * @throws CmsException if something goes wrong
     */
    public CmsUser readOwner(CmsDbContext dbc, CmsProject project) throws CmsException {

        return readUser(dbc, project.getOwnerId());
    }

    /**
     * Reads the parent folder to a given structure id.<p>
     *
     * @param dbc the current database context
     * @param structureId the structure id of the child
     *
     * @return the parent folder resource
     *
     * @throws CmsDataAccessException if something goes wrong
     */
    public CmsResource readParentFolder(CmsDbContext dbc, CmsUUID structureId) throws CmsDataAccessException {

        return getVfsDriver(dbc).readParentFolder(dbc, dbc.currentProject().getUuid(), structureId);
    }

    /**
     * Builds a list of resources for a given path.<p>
     *
     * @param dbc the current database context
     * @param path the requested path
     * @param filter a filter object (only "includeDeleted" information is used!)
     *
     * @return list of <code>{@link CmsResource}</code>s
     *
     * @throws CmsException if something goes wrong
     */
    public List<CmsResource> readPath(CmsDbContext dbc, String path, CmsResourceFilter filter) throws CmsException {

        // splits the path into folder and filename tokens
        List<String> tokens = CmsStringUtil.splitAsList(path, '/');

        // the root folder is no token in the path but a resource which has to be added to the path
        int count = tokens.size() + 1;
        // holds the CmsResource instances in the path
        List<CmsResource> pathList = new ArrayList<CmsResource>(count);

        // true if the path doesn't end with a folder
        boolean lastResourceIsFile = false;
        // number of folders in the path
        int folderCount = count;
        if (!path.endsWith("/")) {
            folderCount--;
            lastResourceIsFile = true;
        }

        // read the root folder, because it's ID is required to read any sub-resources
        String currentResourceName = "/";
        StringBuffer currentPath = new StringBuffer(64);
        currentPath.append('/');

        String cp = currentPath.toString();
        CmsUUID projectId = getProjectIdForContext(dbc);

        // key to cache the resources
        String cacheKey = getCacheKey(null, false, projectId, cp);
        // the current resource
        CmsResource currentResource = m_monitor.getCachedResource(cacheKey);
        if ((currentResource == null) || !dbc.getProjectId().isNullUUID()) {
            currentResource = getVfsDriver(dbc).readFolder(dbc, projectId, cp);
            if (dbc.getProjectId().isNullUUID()) {
                m_monitor.cacheResource(cacheKey, currentResource);
            }
        }

        pathList.add(0, currentResource);

        if (count == 1) {
            // the root folder was requested- no further operations required
            return pathList;
        }

        Iterator<String> it = tokens.iterator();
        currentResourceName = it.next();

        // read the folder resources in the path /a/b/c/
        int i = 0;
        for (i = 1; i < folderCount; i++) {
            currentPath.append(currentResourceName);
            currentPath.append('/');
            // read the folder
            cp = currentPath.toString();
            cacheKey = getCacheKey(null, false, projectId, cp);
            currentResource = m_monitor.getCachedResource(cacheKey);
            if ((currentResource == null) || !dbc.getProjectId().isNullUUID()) {
                currentResource = getVfsDriver(dbc).readFolder(dbc, projectId, cp);
                if (dbc.getProjectId().isNullUUID()) {
                    m_monitor.cacheResource(cacheKey, currentResource);
                }
            }

            pathList.add(i, currentResource);

            if (i < (folderCount - 1)) {
                currentResourceName = it.next();
            }
        }

        // read the (optional) last file resource in the path /x.html
        if (lastResourceIsFile) {
            if (it.hasNext()) {
                // this will only be false if a resource in the
                // top level root folder (e.g. "/index.html") was requested
                currentResourceName = it.next();
            }
            currentPath.append(currentResourceName);

            // read the file
            cp = currentPath.toString();
            cacheKey = getCacheKey(null, false, projectId, cp);
            currentResource = m_monitor.getCachedResource(cacheKey);
            if ((currentResource == null) || !dbc.getProjectId().isNullUUID()) {
                currentResource = getVfsDriver(dbc).readResource(dbc, projectId, cp, filter.includeDeleted());
                if (dbc.getProjectId().isNullUUID()) {
                    m_monitor.cacheResource(cacheKey, currentResource);
                }
            }

            pathList.add(i, currentResource);
        }

        return pathList;
    }

    /**
     * Reads a project given the projects id.<p>
     *
     * @param dbc the current database context
     * @param id the id of the project
     *
     * @return the project read
     *
     * @throws CmsDataAccessException if something goes wrong
     */
    public CmsProject readProject(CmsDbContext dbc, CmsUUID id) throws CmsDataAccessException {

        CmsProject project = null;
        project = m_monitor.getCachedProject(id.toString());
        if (project == null) {
            project = getProjectDriver(dbc).readProject(dbc, id);
            m_monitor.cacheProject(project);
        }
        return project;
    }

    /**
     * Reads a project.<p>
     *
     * Important: Since a project name can be used multiple times, this is NOT the most efficient
     * way to read the project. This is only a convenience for front end developing.
     * Reading a project by name will return the first project with that name.
     * All core classes must use the id version {@link #readProject(CmsDbContext, CmsUUID)} to ensure the right project is read.<p>
     *
     * @param dbc the current database context
     * @param name the name of the project
     *
     * @return the project read
     *
     * @throws CmsException if something goes wrong
     */
    public CmsProject readProject(CmsDbContext dbc, String name) throws CmsException {

        CmsProject project = null;
        project = m_monitor.getCachedProject(name);
        if (project == null) {
            project = getProjectDriver(dbc).readProject(dbc, name);
            m_monitor.cacheProject(project);
        }
        return project;
    }

    /**
     * Returns the list of all resource names that define the "view" of the given project.<p>
     *
     * @param dbc the current database context
     * @param project the project to get the project resources for
     *
     * @return the list of all resources, as <code>{@link String}</code> objects
     *              that define the "view" of the given project.
     *
     * @throws CmsException if something goes wrong
     */
    public List<String> readProjectResources(CmsDbContext dbc, CmsProject project) throws CmsException {

        return getProjectDriver(dbc).readProjectResources(dbc, project);
    }

    /**
     * Reads all resources of a project that match a given state from the VFS.<p>
     *
     * Possible values for the <code>state</code> parameter are:<br>
     * <ul>
     * <li><code>{@link CmsResource#STATE_CHANGED}</code>: Read all "changed" resources in the project</li>
     * <li><code>{@link CmsResource#STATE_NEW}</code>: Read all "new" resources in the project</li>
     * <li><code>{@link CmsResource#STATE_DELETED}</code>: Read all "deleted" resources in the project</li>
     * <li><code>{@link CmsResource#STATE_KEEP}</code>: Read all resources either "changed", "new" or "deleted" in the project</li>
     * </ul><p>
     *
     * @param dbc the current database context
     * @param projectId the id of the project to read the file resources for
     * @param state the resource state to match
     *
     * @return a list of <code>{@link CmsResource}</code> objects matching the filter criteria
     *
     * @throws CmsException if something goes wrong
     *
     * @see CmsObject#readProjectView(CmsUUID, CmsResourceState)
     */
    public List<CmsResource> readProjectView(CmsDbContext dbc, CmsUUID projectId, CmsResourceState state)
    throws CmsException {

        List<CmsResource> resources;
        if (state.isNew() || state.isChanged() || state.isDeleted()) {
            // get all resources form the database that match the selected state
            resources = getVfsDriver(dbc).readResources(dbc, projectId, state, CmsDriverManager.READMODE_MATCHSTATE);
        } else {
            // get all resources form the database that are somehow changed (i.e. not unchanged)
            resources = getVfsDriver(
                dbc).readResources(dbc, projectId, CmsResource.STATE_UNCHANGED, CmsDriverManager.READMODE_UNMATCHSTATE);
        }

        // filter the permissions
        List<CmsResource> result = filterPermissions(dbc, resources, CmsResourceFilter.ALL);
        // sort the result
        Collections.sort(result);
        // set the full resource names
        return updateContextDates(dbc, result);
    }

    /**
     * Reads a property definition.<p>
     *
     * If no property definition with the given name is found,
     * <code>null</code> is returned.<p>
     *
     * @param dbc the current database context
     * @param name the name of the property definition to read
     *
     * @return the property definition that was read
     *
     * @throws CmsException a CmsDbEntryNotFoundException is thrown if the property definition does not exist
     */
    public CmsPropertyDefinition readPropertyDefinition(CmsDbContext dbc, String name) throws CmsException {

        return getVfsDriver(dbc).readPropertyDefinition(dbc, name, dbc.currentProject().getUuid());
    }

    /**
     * Reads a property object from a resource specified by a property name.<p>
     *
     * Returns <code>{@link CmsProperty#getNullProperty()}</code> if the property is not found.<p>
     *
     * @param dbc the current database context
     * @param resource the resource where the property is read from
     * @param key the property key name
     * @param search if <code>true</code>, the property is searched on all parent folders of the resource.
     *      if it's not found attached directly to the resource.
     *
     * @return the required property, or <code>{@link CmsProperty#getNullProperty()}</code> if the property was not found
     *
     * @throws CmsException if something goes wrong
     */
    public CmsProperty readPropertyObject(CmsDbContext dbc, CmsResource resource, String key, boolean search)
    throws CmsException {

        // NOTE: Do not call readPropertyObject(dbc, resource, key, search, null) for performance reasons

        // use the list reading method to obtain all properties for the resource
        List<CmsProperty> properties = readPropertyObjects(dbc, resource, search);

        int i = properties.indexOf(new CmsProperty(key, null, null));
        if (i >= 0) {
            // property has been found in the map
            CmsProperty result = properties.get(i);
            // ensure the result value is not frozen
            return result.cloneAsProperty();
        }
        return CmsProperty.getNullProperty();

    }

    /**
     * Reads a property object from a resource specified by a property name.<p>
     *
     * Returns <code>{@link CmsProperty#getNullProperty()}</code> if the property is not found.<p>
     *
     * @param dbc the current database context
     * @param resource the resource where the property is read from
     * @param key the property key name
     * @param search if <code>true</code>, the property is searched on all parent folders of the resource.
     *      if it's not found attached directly to the resource.
     * @param locale the locale for which the property should be read.
     *
     * @return the required property, or <code>{@link CmsProperty#getNullProperty()}</code> if the property was not found
     *
     * @throws CmsException if something goes wrong
     */
    public CmsProperty readPropertyObject(
        CmsDbContext dbc,
        CmsResource resource,
        String key,
        boolean search,
        Locale locale)
    throws CmsException {

        // use the list reading method to obtain all properties for the resource
        List<CmsProperty> properties = readPropertyObjects(dbc, resource, search);
        // create a lookup property object and look this up in the result map
        CmsProperty result = null;
        // handle the case without locale separately to improve performance
        for (String localizedKey : CmsLocaleManager.getLocaleVariants(key, locale, true, false)) {
            int i = properties.indexOf(new CmsProperty(localizedKey, null, null));
            if (i >= 0) {
                // property has been found in the map
                result = properties.get(i);
                // ensure the result value is not frozen
                return result.cloneAsProperty();
            }
        }
        return CmsProperty.getNullProperty();
    }

    /**
     * Reads all property objects mapped to a specified resource from the database.<p>
     *
     * All properties in the result List will be in frozen (read only) state, so you can't change the values.<p>
     *
     * Returns an empty list if no properties are found at all.<p>
     *
     * @param dbc the current database context
     * @param resource the resource where the properties are read from
     * @param search true, if the properties should be searched on all parent folders  if not found on the resource
     *
     * @return a list of CmsProperty objects containing the structure and/or resource value
     *
     * @throws CmsException if something goes wrong
     *
     * @see CmsObject#readPropertyObjects(String, boolean)
     */
    public List<CmsProperty> readPropertyObjects(CmsDbContext dbc, CmsResource resource, boolean search)
    throws CmsException {

        // check if we have the result already cached
        CmsUUID projectId = getProjectIdForContext(dbc);
        String cacheKey = getCacheKey(CACHE_ALL_PROPERTIES, search, projectId, resource.getRootPath());

        List<CmsProperty> properties = m_monitor.getCachedPropertyList(cacheKey);

        if ((properties == null) || !dbc.getProjectId().isNullUUID()) {
            // result not cached, let's look it up in the DB
            if (search) {
                boolean cont;
                properties = new ArrayList<CmsProperty>();
                List<CmsProperty> parentProperties = null;

                do {
                    try {
                        parentProperties = readPropertyObjects(dbc, resource, false);

                        // make sure properties from lower folders "overwrite" properties from upper folders
                        parentProperties.removeAll(properties);
                        parentProperties.addAll(properties);

                        properties.clear();
                        properties.addAll(parentProperties);

                        cont = resource.getRootPath().length() > 1;
                    } catch (CmsSecurityException se) {
                        // a security exception (probably no read permission) we return the current result
                        cont = false;
                    }
                    if (cont) {
                        // no permission check on parent folder is required since we must have "read"
                        // permissions to read the child resource anyway
                        resource = readResource(
                            dbc,
                            CmsResource.getParentFolder(resource.getRootPath()),
                            CmsResourceFilter.ALL);
                    }
                } while (cont);
            } else {
                properties = getVfsDriver(dbc).readPropertyObjects(dbc, dbc.currentProject(), resource);
                //                for (CmsProperty prop : properties) {
                //                    prop.setOrigin(resource.getRootPath());
                //                }
            }

            // set all properties in the result list as frozen
            CmsProperty.setFrozen(properties);
            if (dbc.getProjectId().isNullUUID()) {
                // store the result in the cache if needed
                m_monitor.cachePropertyList(cacheKey, properties);
            }
        }

        return new ArrayList<CmsProperty>(properties);
    }

    /**
     * Reads the resources that were published in a publish task for a given publish history ID.<p>
     *
     * @param dbc the current database context
     * @param publishHistoryId unique int ID to identify each publish task in the publish history
     *
     * @return a list of <code>{@link org.opencms.db.CmsPublishedResource}</code> objects
     *
     * @throws CmsException if something goes wrong
     */
    public List<CmsPublishedResource> readPublishedResources(CmsDbContext dbc, CmsUUID publishHistoryId)
    throws CmsException {

        String cacheKey = publishHistoryId.toString();
        List<CmsPublishedResource> resourceList = m_monitor.getCachedPublishedResources(cacheKey);
        if ((resourceList == null) || !dbc.getProjectId().isNullUUID()) {
            resourceList = getProjectDriver(dbc).readPublishedResources(dbc, publishHistoryId);
            // store the result in the cache
            if (dbc.getProjectId().isNullUUID()) {
                m_monitor.cachePublishedResources(cacheKey, resourceList);
            }
        }
        return resourceList;
    }

    /**
     * Reads a single publish job identified by its publish history id.<p>
     *
     * @param dbc the current database context
     * @param publishHistoryId unique id to identify the publish job in the publish history
     * @return an object of type <code>{@link CmsPublishJobInfoBean}</code>
     *
     * @throws CmsException if something goes wrong
     */
    public CmsPublishJobInfoBean readPublishJob(CmsDbContext dbc, CmsUUID publishHistoryId) throws CmsException {

        return getProjectDriver(dbc).readPublishJob(dbc, publishHistoryId);
    }

    /**
     * Reads all available publish jobs.<p>
     *
     * @param dbc the current database context
     * @param startTime the start of the time range for finish time
     * @param endTime the end of the time range for finish time
     * @return a list of objects of type <code>{@link CmsPublishJobInfoBean}</code>
     *
     * @throws CmsException if something goes wrong
     */
    public List<CmsPublishJobInfoBean> readPublishJobs(CmsDbContext dbc, long startTime, long endTime)
    throws CmsException {

        return getProjectDriver(dbc).readPublishJobs(dbc, startTime, endTime);
    }

    /**
     * Reads the publish list assigned to a publish job.<p>
     *
     * @param dbc the current database context
     * @param publishHistoryId the history id identifying the publish job
     * @return the assigned publish list
     * @throws CmsException if something goes wrong
     */
    public CmsPublishList readPublishList(CmsDbContext dbc, CmsUUID publishHistoryId) throws CmsException {

        return getProjectDriver(dbc).readPublishList(dbc, publishHistoryId);
    }

    /**
     * Reads the publish report assigned to a publish job.<p>
     *
     * @param dbc the current database context
     * @param publishHistoryId the history id identifying the publish job
     * @return the content of the assigned publish report
     * @throws CmsException if something goes wrong
     */
    public byte[] readPublishReportContents(CmsDbContext dbc, CmsUUID publishHistoryId) throws CmsException {

        return getProjectDriver(dbc).readPublishReportContents(dbc, publishHistoryId);
    }

    /**
     * Reads an historical resource entry for the given resource and with the given version number.<p>
     *
     * @param dbc the current db context
     * @param resource the resource to be read
     * @param version the version number to retrieve
     *
     * @return the resource that was read
     *
     * @throws CmsException if the resource could not be read for any reason
     *
     * @see CmsObject#restoreResourceVersion(CmsUUID, int)
     * @see CmsObject#readResource(CmsUUID, int)
     */
    public I_CmsHistoryResource readResource(CmsDbContext dbc, CmsResource resource, int version) throws CmsException {

        Iterator<I_CmsHistoryResource> itVersions = getHistoryDriver(dbc).readAllAvailableVersions(
            dbc,
            resource.getStructureId()).iterator();
        while (itVersions.hasNext()) {
            I_CmsHistoryResource histRes = itVersions.next();
            if (histRes.getVersion() == version) {
                return histRes;
            }
        }
        throw new CmsVfsResourceNotFoundException(
            org.opencms.db.generic.Messages.get().container(
                org.opencms.db.generic.Messages.ERR_HISTORY_FILE_NOT_FOUND_1,
                resource.getStructureId()));
    }

    /**
     * Reads a resource from the VFS, using the specified resource filter.<p>
     *
     * @param dbc the current database context
     * @param structureID the structure id of the resource to read
     * @param filter the resource filter to use while reading
     *
     * @return the resource that was read
     *
     * @throws CmsDataAccessException if something goes wrong
     *
     * @see CmsObject#readResource(CmsUUID, CmsResourceFilter)
     * @see CmsObject#readResource(CmsUUID)
     */
    public CmsResource readResource(CmsDbContext dbc, CmsUUID structureID, CmsResourceFilter filter)
    throws CmsDataAccessException {

        CmsUUID projectId = getProjectIdForContext(dbc);
        // please note: the filter will be applied in the security manager later
        CmsResource resource = getVfsDriver(dbc).readResource(dbc, projectId, structureID, filter.includeDeleted());

        // context dates need to be updated
        updateContextDates(dbc, resource);

        // return the resource
        return resource;
    }

    /**
     * Reads a resource from the VFS, using the specified resource filter.<p>
     *
     * @param dbc the current database context
     * @param resourcePath the name of the resource to read (full path)
     * @param filter the resource filter to use while reading
     *
     * @return the resource that was read
     *
     * @throws CmsDataAccessException if something goes wrong
     *
     * @see CmsObject#readResource(String, CmsResourceFilter)
     * @see CmsObject#readResource(String)
     * @see CmsObject#readFile(CmsResource)
     */
    public CmsResource readResource(CmsDbContext dbc, String resourcePath, CmsResourceFilter filter)
    throws CmsDataAccessException {

        CmsUUID projectId = getProjectIdForContext(dbc);
        // please note: the filter will be applied in the security manager later
        CmsResource resource = getVfsDriver(dbc).readResource(dbc, projectId, resourcePath, filter.includeDeleted());

        // context dates need to be updated
        updateContextDates(dbc, resource);

        // return the resource
        return resource;
    }

    /**
     * Reads all resources below the given path matching the filter criteria,
     * including the full tree below the path only in case the <code>readTree</code>
     * parameter is <code>true</code>.<p>
     *
     * @param dbc the current database context
     * @param parent the parent path to read the resources from
     * @param filter the filter
     * @param readTree <code>true</code> to read all subresources
     *
     * @return a list of <code>{@link CmsResource}</code> objects matching the filter criteria
     *
     * @throws CmsDataAccessException if the bare reading of the resources fails
     * @throws CmsException if security and permission checks for the resources read fail
     */
    public List<CmsResource> readResources(
        CmsDbContext dbc,
        CmsResource parent,
        CmsResourceFilter filter,
        boolean readTree)
    throws CmsException, CmsDataAccessException {

        // try to get the sub resources from the cache
        String cacheKey = getCacheKey(
            new String[] {dbc.currentUser().getName(), filter.getCacheId(), readTree ? "+" : "-", parent.getRootPath()},
            dbc);

        List<CmsResource> resourceList = m_monitor.getCachedResourceList(cacheKey);
        if ((resourceList == null) || !dbc.getProjectId().isNullUUID()) {
            // read the result from the database
            resourceList = getVfsDriver(dbc).readResourceTree(
                dbc,
                dbc.currentProject().getUuid(),
                (readTree ? parent.getRootPath() : parent.getStructureId().toString()),
                filter.getType(),
                filter.getState(),
                filter.getModifiedAfter(),
                filter.getModifiedBefore(),
                filter.getReleaseAfter(),
                filter.getReleaseBefore(),
                filter.getExpireAfter(),
                filter.getExpireBefore(),
                (readTree ? CmsDriverManager.READMODE_INCLUDE_TREE : CmsDriverManager.READMODE_EXCLUDE_TREE)
                    | (filter.excludeType() ? CmsDriverManager.READMODE_EXCLUDE_TYPE : 0)
                    | (filter.excludeState() ? CmsDriverManager.READMODE_EXCLUDE_STATE : 0)
                    | ((filter.getOnlyFolders() != null)
                    ? (filter.getOnlyFolders().booleanValue()
                    ? CmsDriverManager.READMODE_ONLY_FOLDERS
                    : CmsDriverManager.READMODE_ONLY_FILES)
                    : 0));

            // HACK: do not take care of permissions if reading organizational units
            if (!parent.getRootPath().startsWith("/system/orgunits/")) {
                // apply permission filter
                resourceList = filterPermissions(dbc, resourceList, filter);
            }
            // store the result in the resourceList cache
            if (dbc.getProjectId().isNullUUID()) {
                m_monitor.cacheResourceList(cacheKey, resourceList);
            }
        }
        // we must always apply the result filter and update the context dates
        return updateContextDates(dbc, resourceList, filter);
    }

    /**
     * Returns the resources that were visited by a user set in the filter.<p>
     *
     * @param dbc the database context
     * @param poolName the name of the database pool to use
     * @param filter the filter that is used to get the visited resources
     *
     * @return the resources that were visited by a user set in the filter
     *
     * @throws CmsException if something goes wrong
     */
    public List<CmsResource> readResourcesVisitedBy(CmsDbContext dbc, String poolName, CmsVisitedByFilter filter)
    throws CmsException {

        List<CmsResource> result = getSubscriptionDriver().readResourcesVisitedBy(dbc, poolName, filter);
        result = filterPermissions(dbc, result, CmsResourceFilter.DEFAULT);
        return result;
    }

    /**
     * Reads all resources that have a value (containing the given value string) set
     * for the specified property (definition) in the given path.<p>
     *
     * Both individual and shared properties of a resource are checked.<p>
     *
     * If the <code>value</code> parameter is <code>null</code>, all resources having the
     * given property set are returned.<p>
     *
     * @param dbc the current database context
     * @param folder the folder to get the resources with the property from
     * @param propertyDefinition the name of the property (definition) to check for
     * @param value the string to search in the value of the property
     * @param filter the resource filter to apply to the result set
     *
     * @return a list of all <code>{@link CmsResource}</code> objects
     *          that have a value set for the specified property.
     *
     * @throws CmsException if something goes wrong
     */
    public List<CmsResource> readResourcesWithProperty(
        CmsDbContext dbc,
        CmsResource folder,
        String propertyDefinition,
        String value,
        CmsResourceFilter filter)
    throws CmsException {

        String cacheKey;
        if (value == null) {
            cacheKey = getCacheKey(
                new String[] {
                    dbc.currentUser().getName(),
                    folder.getRootPath(),
                    propertyDefinition,
                    filter.getCacheId()},
                dbc);
        } else {
            cacheKey = getCacheKey(
                new String[] {
                    dbc.currentUser().getName(),
                    folder.getRootPath(),
                    propertyDefinition,
                    value,
                    filter.getCacheId()},
                dbc);
        }
        List<CmsResource> resourceList = m_monitor.getCachedResourceList(cacheKey);
        if ((resourceList == null) || !dbc.getProjectId().isNullUUID()) {
            // first read the property definition
            CmsPropertyDefinition propDef = readPropertyDefinition(dbc, propertyDefinition);
            // now read the list of resources that have a value set for the property definition
            resourceList = getVfsDriver(dbc).readResourcesWithProperty(
                dbc,
                dbc.currentProject().getUuid(),
                propDef.getId(),
                folder.getRootPath(),
                value);
            // apply permission filter
            resourceList = filterPermissions(dbc, resourceList, filter);
            // store the result in the resourceList cache
            if (dbc.getProjectId().isNullUUID()) {
                m_monitor.cacheResourceList(cacheKey, resourceList);
            }
        }
        // we must always apply the result filter and update the context dates
        return updateContextDates(dbc, resourceList, filter);
    }

    /**
     * Returns the set of users that are responsible for a specific resource.<p>
     *
     * @param dbc the current database context
     * @param resource the resource to get the responsible users from
     *
     * @return the set of users that are responsible for a specific resource
     *
     * @throws CmsException if something goes wrong
     */
    public Set<I_CmsPrincipal> readResponsiblePrincipals(CmsDbContext dbc, CmsResource resource) throws CmsException {

        Set<I_CmsPrincipal> result = new HashSet<I_CmsPrincipal>();
        Iterator<CmsAccessControlEntry> aces = getAccessControlEntries(dbc, resource, true).iterator();
        while (aces.hasNext()) {
            CmsAccessControlEntry ace = aces.next();
            if (ace.isResponsible()) {
                I_CmsPrincipal p = lookupPrincipal(dbc, ace.getPrincipal());
                if (p != null) {
                    result.add(p);
                }
            }
        }
        return result;
    }

    /**
     * Returns the set of users that are responsible for a specific resource.<p>
     *
     * @param dbc the current database context
     * @param resource the resource to get the responsible users from
     *
     * @return the set of users that are responsible for a specific resource
     *
     * @throws CmsException if something goes wrong
     */
    public Set<CmsUser> readResponsibleUsers(CmsDbContext dbc, CmsResource resource) throws CmsException {

        Set<CmsUser> result = new HashSet<CmsUser>();
        Iterator<I_CmsPrincipal> principals = readResponsiblePrincipals(dbc, resource).iterator();
        while (principals.hasNext()) {
            I_CmsPrincipal principal = principals.next();
            if (principal.isGroup()) {
                try {
                    result.addAll(getUsersOfGroup(dbc, principal.getName(), true, false, false));
                } catch (CmsException e) {
                    if (LOG.isInfoEnabled()) {
                        LOG.info(e);
                    }
                }
            } else {
                result.add((CmsUser)principal);
            }
        }
        return result;
    }

    /**
     * Returns a List of all siblings of the specified resource,
     * the specified resource being always part of the result set.<p>
     *
     * The result is a list of <code>{@link CmsResource}</code> objects.<p>
     *
     * @param dbc the current database context
     * @param resource the resource to read the siblings for
     * @param filter a filter object
     *
     * @return a list of <code>{@link CmsResource}</code> Objects that
     *          are siblings to the specified resource,
     *          including the specified resource itself
     *
     * @throws CmsException if something goes wrong
     */
    public List<CmsResource> readSiblings(CmsDbContext dbc, CmsResource resource, CmsResourceFilter filter)
    throws CmsException {

        List<CmsResource> siblings = getVfsDriver(
            dbc).readSiblings(dbc, dbc.currentProject().getUuid(), resource, filter.includeDeleted());

        // important: there is no permission check done on the returned list of siblings
        // this is because of possible issues with the "publish all siblings" option,
        // moreover the user has read permission for the content through
        // the selected sibling anyway
        return updateContextDates(dbc, siblings, filter);
    }

    /**
     * Returns the parameters of a resource in the table of all published template resources.<p>
     *
     * @param dbc the current database context
     * @param rfsName the rfs name of the resource
     *
     * @return the parameter string of the requested resource
     *
     * @throws CmsException if something goes wrong
     */
    public String readStaticExportPublishedResourceParameters(CmsDbContext dbc, String rfsName) throws CmsException {

        return getProjectDriver(dbc).readStaticExportPublishedResourceParameters(dbc, rfsName);
    }

    /**
     * Returns a list of all template resources which must be processed during a static export.<p>
     *
     * @param dbc the current database context
     * @param parameterResources flag for reading resources with parameters (1) or without (0)
     * @param timestamp for reading the data from the db
     *
     * @return a list of template resources as <code>{@link String}</code> objects
     *
     * @throws CmsException if something goes wrong
     */
    public List<String> readStaticExportResources(CmsDbContext dbc, int parameterResources, long timestamp)
    throws CmsException {

        return getProjectDriver(dbc).readStaticExportResources(dbc, parameterResources, timestamp);
    }

    /**
     * Returns the subscribed history resources that were deleted.<p>
     *
     * @param dbc the database context
     * @param poolName the name of the database pool to use
     * @param user the user that subscribed to the resource
     * @param groups the groups to check subscribed resources for
     * @param parent the parent resource (folder) of the deleted resources, if <code>null</code> all deleted resources will be returned
     * @param includeSubFolders indicates if the sub folders of the specified folder path should be considered, too
     * @param deletedFrom the time stamp from which the resources should have been deleted
     *
     * @return the subscribed history resources that were deleted
     *
     * @throws CmsException if something goes wrong
     */
    public List<I_CmsHistoryResource> readSubscribedDeletedResources(
        CmsDbContext dbc,
        String poolName,
        CmsUser user,
        List<CmsGroup> groups,
        CmsResource parent,
        boolean includeSubFolders,
        long deletedFrom)
    throws CmsException {

        List<I_CmsHistoryResource> result = getSubscriptionDriver().readSubscribedDeletedResources(
            dbc,
            poolName,
            user,
            groups,
            parent,
            includeSubFolders,
            deletedFrom);

        return result;
    }

    /**
     * Returns the resources that were subscribed by a user or group set in the filter.<p>
     *
     * @param dbc the database context
     * @param poolName the name of the database pool to use
     * @param filter the filter that is used to get the subscribed resources
     *
     * @return the resources that were subscribed by a user or group set in the filter
     *
     * @throws CmsException if something goes wrong
     */
    public List<CmsResource> readSubscribedResources(CmsDbContext dbc, String poolName, CmsSubscriptionFilter filter)
    throws CmsException {

        List<CmsResource> result = getSubscriptionDriver().readSubscribedResources(dbc, poolName, filter);

        result = filterPermissions(dbc, result, CmsResourceFilter.DEFAULT);
        return result;
    }

    /**
     * Reads URL name mapping entries which match the given filter.<p>
     *
     * @param dbc the database context
     * @param online if true, read online URL name mappings, else offline ones
     * @param filter the filter for matching the URL name entries
     *
     * @return the list of URL name mapping entries which match the given filter
     *
     * @throws CmsDataAccessException if something goes wrong
     */
    public List<CmsUrlNameMappingEntry> readUrlNameMappingEntries(
        CmsDbContext dbc,
        boolean online,
        CmsUrlNameMappingFilter filter)
    throws CmsDataAccessException {

        I_CmsVfsDriver vfsDriver = getVfsDriver(dbc);
        return vfsDriver.readUrlNameMappingEntries(dbc, online, filter);
    }

    /**
     * Reads the URL name mappings matching the given filter.<p>
     *
     * @param dbc the DB context to use
     * @param filter the filter used to select the mapping entries
     * @return the entries matching the given filter
     *
     * @throws CmsDataAccessException if something goes wrong
     */
    public List<CmsUrlNameMappingEntry> readUrlNameMappings(CmsDbContext dbc, CmsUrlNameMappingFilter filter)
    throws CmsDataAccessException {

        List<CmsUrlNameMappingEntry> entries = getVfsDriver(dbc).readUrlNameMappingEntries(
            dbc,
            dbc.currentProject().isOnlineProject(),
            filter);
        return entries;
    }

    /**
     * Reads the newest URL names of a resource for all locales.<p>
     *
     * @param dbc the database context
     * @param id the resource's structure id
     *
     * @return the url names for the locales
     *
     * @throws CmsDataAccessException if the database operation failed
     */
    public List<String> readUrlNamesForAllLocales(CmsDbContext dbc, CmsUUID id) throws CmsDataAccessException {

        List<String> result = new ArrayList<String>();
        List<CmsUrlNameMappingEntry> entries = getVfsDriver(dbc).readUrlNameMappingEntries(
            dbc,
            dbc.currentProject().isOnlineProject(),
            CmsUrlNameMappingFilter.ALL.filterStructureId(id));
        ArrayListMultimap<String, CmsUrlNameMappingEntry> entriesByLocale = ArrayListMultimap.create();
        for (CmsUrlNameMappingEntry entry : entries) {
            String localeKey = entry.getLocale();
            entriesByLocale.put(localeKey, entry);
        }

        for (String localeKey : entriesByLocale.keySet()) {
            List<CmsUrlNameMappingEntry> entrs = entriesByLocale.get(localeKey);
            CmsUrlNameMappingEntry maxEntryForLocale = Collections.max(entrs, new UrlNameMappingComparator());
            result.add(maxEntryForLocale.getName());
        }
        return result;
    }

    /**
     * Returns a user object based on the id of a user.<p>
     *
     * @param dbc the current database context
     * @param id the id of the user to read
     *
     * @return the user read
     *
     * @throws CmsException if something goes wrong
     */
    public CmsUser readUser(CmsDbContext dbc, CmsUUID id) throws CmsException {

        CmsUser user = m_monitor.getCachedUser(id.toString());
        if (user == null) {
            user = getUserDriver(dbc).readUser(dbc, id);
            m_monitor.cacheUser(user);
        }
        // important: do not return the cached user object, but a clone to avoid unwanted changes on cached objects
        return user.clone();
    }

    /**
     * Returns a user object.<p>
     *
     * @param dbc the current database context
     * @param username the name of the user that is to be read
     *
     * @return user read
     *
     * @throws CmsDataAccessException if operation was not successful
     */
    public CmsUser readUser(CmsDbContext dbc, String username) throws CmsDataAccessException {

        CmsUser user = m_monitor.getCachedUser(username);
        if (user == null) {
            user = getUserDriver(dbc).readUser(dbc, username);
            m_monitor.cacheUser(user);
        }
        // important: do not return the cached user object, but a clone to avoid unwanted changes on cached objects
        return user.clone();
    }

    /**
     * Returns a user object if the password for the user is correct.<p>
     *
     * If the user/pwd pair is not valid a <code>{@link CmsException}</code> is thrown.<p>
     *
     * @param dbc the current database context
     * @param username the username of the user that is to be read
     * @param password the password of the user that is to be read
     *
     * @return user read
     *
     * @throws CmsException if operation was not successful
     */
    public CmsUser readUser(CmsDbContext dbc, String username, String password) throws CmsException {

        // don't read user from cache here because password may have changed
        CmsUser user = getUserDriver(dbc).readUser(dbc, username, password, null);
        m_monitor.cacheUser(user);
        return user;
    }

    /**
     * Removes an access control entry for a given resource and principal.<p>
     *
     * @param dbc the current database context
     * @param resource the resource
     * @param principal the id of the principal to remove the the access control entry for
     *
     * @throws CmsException if something goes wrong
     */
    public void removeAccessControlEntry(CmsDbContext dbc, CmsResource resource, CmsUUID principal)
    throws CmsException {

        // remove the ace
        getUserDriver(dbc).removeAccessControlEntry(dbc, dbc.currentProject(), resource.getResourceId(), principal);

        // log it
        log(
            dbc,
            new CmsLogEntry(
                dbc,
                resource.getStructureId(),
                CmsLogEntryType.RESOURCE_PERMISSIONS,
                new String[] {resource.getRootPath()}),
            false);

        // update the "last modified" information
        setDateLastModified(dbc, resource, resource.getDateLastModified());

        // clear the cache
        m_monitor.clearAccessControlListCache();

        // fire a resource modification event
        Map<String, Object> data = new HashMap<String, Object>(2);
        data.put(I_CmsEventListener.KEY_RESOURCE, resource);
        data.put(I_CmsEventListener.KEY_CHANGE, new Integer(CHANGED_ACCESSCONTROL));
        OpenCms.fireCmsEvent(new CmsEvent(I_CmsEventListener.EVENT_RESOURCE_MODIFIED, data));
    }

    /**
     * Removes a resource from the given organizational unit.<p>
     *
     * @param dbc the current db context
     * @param orgUnit the organizational unit to remove the resource from
     * @param resource the resource that is to be removed from the organizational unit
     *
     * @throws CmsException if something goes wrong
     *
     * @see org.opencms.security.CmsOrgUnitManager#addResourceToOrgUnit(CmsObject, String, String)
     * @see org.opencms.security.CmsOrgUnitManager#addResourceToOrgUnit(CmsObject, String, String)
     */
    public void removeResourceFromOrgUnit(CmsDbContext dbc, CmsOrganizationalUnit orgUnit, CmsResource resource)
    throws CmsException {

        m_monitor.flushCache(CmsMemoryMonitor.CacheType.HAS_ROLE, CmsMemoryMonitor.CacheType.ROLE_LIST);
        getUserDriver(dbc).removeResourceFromOrganizationalUnit(dbc, orgUnit, resource);
    }

    /**
     * Removes a resource from the current project of the user.<p>
     *
     * @param dbc the current database context
     * @param resource the resource to apply this operation to
     *
     * @throws CmsException if something goes wrong
     *
     * @see CmsObject#copyResourceToProject(String)
     * @see I_CmsResourceType#copyResourceToProject(CmsObject, CmsSecurityManager, CmsResource)
     */
    public void removeResourceFromProject(CmsDbContext dbc, CmsResource resource) throws CmsException {

        // remove the resource to the project only if the resource is already in the project
        if (isInsideCurrentProject(dbc, resource.getRootPath())) {
            // check if there are already any subfolders of this resource
            I_CmsProjectDriver projectDriver = getProjectDriver(dbc);
            if (resource.isFolder()) {
                List<String> projectResources = projectDriver.readProjectResources(dbc, dbc.currentProject());
                for (int i = 0; i < projectResources.size(); i++) {
                    String resname = projectResources.get(i);
                    if (resname.startsWith(resource.getRootPath())) {
                        // delete the existing project resource first
                        projectDriver.deleteProjectResource(dbc, dbc.currentProject().getUuid(), resname);
                    }
                }
            }
            try {
                projectDriver.deleteProjectResource(dbc, dbc.currentProject().getUuid(), resource.getRootPath());
            } catch (CmsException exc) {
                // if the subfolder exists already - all is ok
            } finally {
                m_monitor.flushCache(CmsMemoryMonitor.CacheType.PROJECT_RESOURCES);

                OpenCms.fireCmsEvent(
                    new CmsEvent(
                        I_CmsEventListener.EVENT_PROJECT_MODIFIED,
                        Collections.<String, Object> singletonMap("project", dbc.currentProject())));
            }
        }
    }

    /**
     * Removes the given resource to the given user's publish list.<p>
     *
     * @param dbc the database context
     * @param userId the user's id
     * @param structureIds the collection of structure IDs to remove
     *
     * @throws CmsDataAccessException if something goes wrong
     */
    public void removeResourceFromUsersPubList(CmsDbContext dbc, CmsUUID userId, Collection<CmsUUID> structureIds)
    throws CmsDataAccessException {

        for (CmsUUID structureId : structureIds) {
            CmsLogEntry entry = new CmsLogEntry(
                userId,
                System.currentTimeMillis(),
                structureId,
                CmsLogEntryType.RESOURCE_HIDDEN,
                new String[] {readResource(dbc, structureId, CmsResourceFilter.ALL).getRootPath()});
            log(dbc, entry, true);
        }
    }

    /**
     * Removes a user from a group.<p>
     *
     * @param dbc the current database context
     * @param username the name of the user that is to be removed from the group
     * @param groupname the name of the group
     * @param readRoles if to read roles or groups
     *
     * @throws CmsException if operation was not successful
     * @throws CmsIllegalArgumentException if the given user was not member in the given group
     * @throws CmsDbEntryNotFoundException if the given group was not found
     * @throws CmsSecurityException if the given user was <b>read as 'null' from the database</b>
     *
     * @see #addUserToGroup(CmsDbContext, String, String, boolean)
     */
    public void removeUserFromGroup(CmsDbContext dbc, String username, String groupname, boolean readRoles)
    throws CmsException, CmsIllegalArgumentException, CmsDbEntryNotFoundException, CmsSecurityException {

        CmsGroup group = readGroup(dbc, groupname);
        //check if group exists
        if (group == null) {
            // the group does not exists
            throw new CmsDbEntryNotFoundException(Messages.get().container(Messages.ERR_UNKNOWN_GROUP_1, groupname));
        }
        if (group.isVirtual() && !readRoles) {
            // if removing a user from a virtual role treat it as removing the user from the role
            removeUserFromGroup(dbc, username, CmsRole.valueOf(group).getGroupName(), true);
            return;
        }
        if (group.isVirtual()) {
            // this is an hack so to prevent a unlimited recursive calls
            readRoles = false;
        }
        if ((readRoles && !group.isRole()) || (!readRoles && group.isRole())) {
            // we want a role but we got a group, or the other way
            throw new CmsDbEntryNotFoundException(Messages.get().container(Messages.ERR_UNKNOWN_GROUP_1, groupname));
        }

        boolean skipRemove = false;
        // test if this user is existing in the group
        if (!userInGroup(dbc, username, groupname, readRoles)) {
            if (readRoles) {
                // Sometimes users can end up with the default groups corresponding to roles (Administrators, Users) without the actual roles.
                // When trying to remove the user from such a group, we end up here in a recursive call of this method with readRoles = true. We do not
                // want to throw an exception then, because it would prevent the code that actually removes the user from the group from running.
                LOG.warn(
                    "Trying to remove user from role that they are not a member of (user: "
                        + username
                        + ", group: "
                        + groupname
                        + ")");
                skipRemove = true;
            } else {
                // user is not in the group, throw exception
                throw new CmsIllegalArgumentException(
                    Messages.get().container(Messages.ERR_USER_NOT_IN_GROUP_2, username, groupname));
            }
        }

        CmsUser user = readUser(dbc, username);
        //check if the user exists
        if (user == null) {
            // the user does not exists
            throw new CmsIllegalArgumentException(
                Messages.get().container(Messages.ERR_USER_NOT_IN_GROUP_2, username, groupname));
        }

        if (readRoles) {
            CmsRole role = CmsRole.valueOf(group);
            // update virtual groups
            Iterator<CmsGroup> it = getVirtualGroupsForRole(dbc, role).iterator();
            while (it.hasNext()) {
                CmsGroup virtualGroup = it.next();
                if (userInGroup(dbc, username, virtualGroup.getName(), false)) {
                    // here we say readroles = true, to prevent an unlimited recursive calls
                    removeUserFromGroup(dbc, username, virtualGroup.getName(), true);
                }
            }
        }
        if (!skipRemove) {
            getUserDriver(dbc).deleteUserInGroup(dbc, user.getId(), group.getId());
        }

        // flush relevant caches
        if (readRoles) {
            m_monitor.flushCache(CmsMemoryMonitor.CacheType.HAS_ROLE, CmsMemoryMonitor.CacheType.ROLE_LIST);
        }
        m_monitor.flushCache(CmsMemoryMonitor.CacheType.USERGROUPS, CmsMemoryMonitor.CacheType.USER_LIST);

        if (!dbc.getProjectId().isNullUUID()) {
            // user modified event is not needed
            return;
        }
        // fire user modified event
        Map<String, Object> eventData = new HashMap<String, Object>();
        eventData.put(I_CmsEventListener.KEY_USER_ID, user.getId().toString());
        eventData.put(I_CmsEventListener.KEY_USER_NAME, user.getName());
        eventData.put(I_CmsEventListener.KEY_GROUP_ID, group.getId().toString());
        eventData.put(I_CmsEventListener.KEY_GROUP_NAME, group.getName());
        eventData.put(
            I_CmsEventListener.KEY_USER_ACTION,
            I_CmsEventListener.VALUE_USER_MODIFIED_ACTION_REMOVE_USER_FROM_GROUP);
        OpenCms.fireCmsEvent(new CmsEvent(I_CmsEventListener.EVENT_USER_MODIFIED, eventData));

    }

    /**
     * Repairs broken categories.<p>
     *
     * @param dbc the database context
     * @param projectId the project id
     * @param resource the resource to repair the categories for
     *
     * @throws CmsException if something goes wrong
     */
    public void repairCategories(CmsDbContext dbc, CmsUUID projectId, CmsResource resource) throws CmsException {

        CmsObject cms = OpenCms.initCmsObject(new CmsObject(getSecurityManager(), dbc.getRequestContext()));
        cms.getRequestContext().setSiteRoot("");
        cms.getRequestContext().setCurrentProject(readProject(dbc, projectId));
        CmsCategoryService.getInstance().repairRelations(cms, resource);
    }

    /**
     * Replaces the content, type and properties of a resource.<p>
     *
     * @param dbc the current database context
     * @param resource the name of the resource to apply this operation to
     * @param type the new type of the resource
     * @param content the new content of the resource
     * @param properties the new properties of the resource
     *
     * @throws CmsException if something goes wrong
     *
     * @see CmsObject#replaceResource(String, int, byte[], List)
     * @see I_CmsResourceType#replaceResource(CmsObject, CmsSecurityManager, CmsResource, int, byte[], List)
     */
    @SuppressWarnings("javadoc")
    public void replaceResource(
        CmsDbContext dbc,
        CmsResource resource,
        int type,
        byte[] content,
        List<CmsProperty> properties)
    throws CmsException {

        // replace the existing with the new file content
        getVfsDriver(dbc).replaceResource(dbc, resource, content, type);

        if ((properties != null) && !properties.isEmpty()) {
            // write the properties
            getVfsDriver(dbc).writePropertyObjects(dbc, dbc.currentProject(), resource, properties);
            m_monitor.flushCache(CmsMemoryMonitor.CacheType.PROPERTY, CmsMemoryMonitor.CacheType.PROPERTY_LIST);
        }

        // update the resource state
        if (resource.getState().isUnchanged()) {
            resource.setState(CmsResource.STATE_CHANGED);
        }
        resource.setUserLastModified(dbc.currentUser().getId());

        // log it
        log(
            dbc,
            new CmsLogEntry(
                dbc,
                resource.getStructureId(),
                CmsLogEntryType.RESOURCE_CONTENT_MODIFIED,
                new String[] {resource.getRootPath()}),
            false);

        setDateLastModified(dbc, resource, System.currentTimeMillis());

        getVfsDriver(dbc).writeResourceState(dbc, dbc.currentProject(), resource, UPDATE_RESOURCE, false);

        deleteRelationsWithSiblings(dbc, resource);

        // clear the cache
        m_monitor.clearResourceCache();

        if ((properties != null) && !properties.isEmpty()) {
            // resource and properties were modified
            OpenCms.fireCmsEvent(
                new CmsEvent(
                    I_CmsEventListener.EVENT_RESOURCE_AND_PROPERTIES_MODIFIED,
                    Collections.<String, Object> singletonMap(I_CmsEventListener.KEY_RESOURCE, resource)));
        } else {
            // only the resource was modified
            Map<String, Object> data = new HashMap<String, Object>(2);
            data.put(I_CmsEventListener.KEY_RESOURCE, resource);
            data.put(I_CmsEventListener.KEY_CHANGE, new Integer(CHANGED_RESOURCE | CHANGED_CONTENT));
            OpenCms.fireCmsEvent(new CmsEvent(I_CmsEventListener.EVENT_RESOURCE_MODIFIED, data));
        }
    }

    /**
     * Resets the password for a specified user.<p>
     *
     * @param dbc the current database context
     * @param username the name of the user
     * @param oldPassword the old password
     * @param newPassword the new password
     *
     * @throws CmsException if the user data could not be read from the database
     * @throws CmsSecurityException if the specified username and old password could not be verified
     */
    public void resetPassword(CmsDbContext dbc, String username, String oldPassword, String newPassword)
    throws CmsException, CmsSecurityException {

        if ((oldPassword != null) && (newPassword != null)) {

            CmsUser user = null;

            validatePassword(newPassword);

            // read the user as a system user to verify that the specified old password is correct
            try {
                user = getUserDriver(dbc).readUser(dbc, username, oldPassword, null);
            } catch (CmsDbEntryNotFoundException e) {
                throw new CmsDataAccessException(Messages.get().container(Messages.ERR_RESET_PASSWORD_1, username), e);
            }

            if ((user == null) || user.isManaged()) {
                throw new CmsDataAccessException(Messages.get().container(Messages.ERR_RESET_PASSWORD_1, username));
            }

            getUserDriver(dbc).writePassword(dbc, username, oldPassword, newPassword);
            user.getAdditionalInfo().put(
                CmsUserSettings.ADDITIONAL_INFO_LAST_PASSWORD_CHANGE,
                "" + System.currentTimeMillis());
            user.deleteAdditionalInfo(CmsUserSettings.ADDITIONAL_INFO_PASSWORD_RESET);
            getUserDriver(dbc).writeUser(dbc, user);

            if (!dbc.getProjectId().isNullUUID()) {
                // user modified event is not needed
                return;
            }
            // fire user modified event
            Map<String, Object> eventData = new HashMap<String, Object>();
            eventData.put(I_CmsEventListener.KEY_USER_ID, user.getId().toString());
            eventData.put(
                I_CmsEventListener.KEY_USER_ACTION,
                I_CmsEventListener.VALUE_USER_MODIFIED_ACTION_RESET_PASSWORD);
            eventData.put(
                I_CmsEventListener.KEY_USER_CHANGES,
                Integer.valueOf(CmsUser.FLAG_CORE_DATA | CmsUser.FLAG_CORE_DATA));
            OpenCms.fireCmsEvent(new CmsEvent(I_CmsEventListener.EVENT_USER_MODIFIED, eventData));

        } else if (CmsStringUtil.isEmpty(oldPassword)) {
            throw new CmsDataAccessException(Messages.get().container(Messages.ERR_PWD_OLD_MISSING_0));
        } else if (CmsStringUtil.isEmpty(newPassword)) {
            throw new CmsDataAccessException(Messages.get().container(Messages.ERR_PWD_NEW_MISSING_0));
        }
    }

    /**
     * Restores a deleted resource identified by its structure id from the historical archive.<p>
     *
     * @param dbc the current database context
     * @param structureId the structure id of the resource to restore
     *
     * @throws CmsException if something goes wrong
     *
     * @see CmsObject#restoreDeletedResource(CmsUUID)
     */
    public void restoreDeletedResource(CmsDbContext dbc, CmsUUID structureId) throws CmsException {

        // get the last version, which should be the deleted one
        int version = getHistoryDriver(dbc).readLastVersion(dbc, structureId);
        // get that version
        I_CmsHistoryResource histRes = getHistoryDriver(dbc).readResource(dbc, structureId, version);

        // check the parent path
        CmsResource parent;
        try {
            // try to read the parent resource by id
            parent = getVfsDriver(dbc).readResource(dbc, dbc.currentProject().getUuid(), histRes.getParentId(), true);
        } catch (CmsVfsResourceNotFoundException e) {
            // if not found try to read the parent resource by name
            try {
                // try to read the parent resource by id
                parent = getVfsDriver(dbc).readResource(
                    dbc,
                    dbc.currentProject().getUuid(),
                    CmsResource.getParentFolder(histRes.getRootPath()),
                    true);
            } catch (CmsVfsResourceNotFoundException e1) {
                // if not found try to restore the parent resource
                restoreDeletedResource(dbc, histRes.getParentId());
                parent = readResource(dbc, histRes.getParentId(), CmsResourceFilter.IGNORE_EXPIRATION);
            }
        }
        // check write permissions
        m_securityManager.checkPermissions(
            dbc,
            parent,
            CmsPermissionSet.ACCESS_WRITE,
            false,
            CmsResourceFilter.IGNORE_EXPIRATION);

        // check the name
        String path = CmsResource.getParentFolder(histRes.getRootPath()); // path
        String resName = CmsResource.getName(histRes.getRootPath()); // name
        String ext = "";
        if (resName.charAt(resName.length() - 1) == '/') {
            resName = resName.substring(0, resName.length() - 1);
        } else {
            ext = CmsFileUtil.getExtension(resName); // extension
        }
        String nameWOExt = resName.substring(0, resName.length() - ext.length()); // name without extension
        for (int i = 1; true; i++) {
            try {
                readResource(dbc, path + resName, CmsResourceFilter.ALL);
                resName = nameWOExt + "_" + i + ext;
                // try the next resource name with following schema: path/name_{i}.ext
            } catch (CmsVfsResourceNotFoundException e) {
                // ok, we found a not used resource name
                break;
            }
        }

        // check structure id
        CmsUUID id = structureId;
        if (getVfsDriver(dbc).validateStructureIdExists(dbc, dbc.currentProject().getUuid(), structureId)) {
            // should never happen, but if already exists create a new one
            id = new CmsUUID();
        }

        byte[] contents = null;
        boolean isFolder = true;

        // do we need the contents?
        if (histRes instanceof CmsFile) {
            contents = ((CmsFile)histRes).getContents();
            if ((contents == null) || (contents.length == 0)) {
                contents = getHistoryDriver(dbc).readContent(dbc, histRes.getResourceId(), histRes.getPublishTag());
            }
            isFolder = false;
        }

        // now read the historical properties
        List<CmsProperty> properties = getHistoryDriver(dbc).readProperties(dbc, histRes);

        // create the object to create
        CmsResource newResource = new CmsResource(
            id,
            histRes.getResourceId(),
            path + resName,
            histRes.getTypeId(),
            isFolder,
            histRes.getFlags(),
            dbc.currentProject().getUuid(),
            CmsResource.STATE_NEW,
            histRes.getDateCreated(),
            histRes.getUserCreated(),
            histRes.getDateLastModified(),
            dbc.currentUser().getId(),
            histRes.getDateReleased(),
            histRes.getDateExpired(),
            histRes.getSiblingCount(),
            histRes.getLength(),
            histRes.getDateContent(),
            histRes.getVersion());

        // log it
        log(
            dbc,
            new CmsLogEntry(
                dbc,
                newResource.getStructureId(),
                CmsLogEntryType.RESOURCE_RESTORE_DELETED,
                new String[] {newResource.getRootPath()}),
            false);

        // prevent the date last modified is set to the current time
        newResource.setDateLastModified(newResource.getDateLastModified());
        // restore the resource!
        CmsResource resource = createResource(dbc, path + resName, newResource, contents, properties, true);
        // set resource state to changed
        newResource.setState(CmsResource.STATE_CHANGED);
        getVfsDriver(dbc).writeResourceState(dbc, dbc.currentProject(), newResource, UPDATE_RESOURCE_STATE, false);
        newResource.setState(CmsResource.STATE_NEW);
        // fire the event
        Map<String, Object> data = new HashMap<String, Object>(2);
        data.put(I_CmsEventListener.KEY_RESOURCE, resource);
        data.put(I_CmsEventListener.KEY_CHANGE, new Integer(CHANGED_RESOURCE | CHANGED_CONTENT));
        OpenCms.fireCmsEvent(new CmsEvent(I_CmsEventListener.EVENT_RESOURCE_MODIFIED, data));
    }

    /**
     * Restores a resource in the current project with a version from the historical archive.<p>
     *
     * @param dbc the current database context
     * @param resource the resource to restore from the archive
     * @param version the version number to restore from the archive
     *
     * @throws CmsException if something goes wrong
     *
     * @see CmsObject#restoreResourceVersion(CmsUUID, int)
     * @see I_CmsResourceType#restoreResource(CmsObject, CmsSecurityManager, CmsResource, int)
     */
    public void restoreResource(CmsDbContext dbc, CmsResource resource, int version) throws CmsException {

        I_CmsHistoryResource historyResource = readResource(dbc, resource, version);
        CmsResourceState state = CmsResource.STATE_CHANGED;
        if (resource.getState().isNew()) {
            state = CmsResource.STATE_NEW;
        }
        int newVersion = resource.getVersion();
        if (resource.getState().isUnchanged()) {
            newVersion++;
        }
        CmsResource newResource = null;
        // is the resource a file?
        if (historyResource instanceof CmsFile) {
            // get the historical up flags
            int flags = historyResource.getFlags();
            if (resource.isLabeled()) {
                // set the flag for labeled links on the restored file
                flags |= CmsResource.FLAG_LABELED;
            }
            CmsFile newFile = new CmsFile(
                resource.getStructureId(),
                resource.getResourceId(),
                resource.getRootPath(),
                historyResource.getTypeId(),
                flags,
                dbc.currentProject().getUuid(),
                state,
                resource.getDateCreated(),
                historyResource.getUserCreated(),
                resource.getDateLastModified(),
                dbc.currentUser().getId(),
                historyResource.getDateReleased(),
                historyResource.getDateExpired(),
                resource.getSiblingCount(),
                historyResource.getLength(),
                historyResource.getDateContent(),
                newVersion,
                readFile(dbc, (CmsHistoryFile)historyResource).getContents());

            // log it
            log(
                dbc,
                new CmsLogEntry(
                    dbc,
                    newFile.getStructureId(),
                    CmsLogEntryType.RESOURCE_HISTORY,
                    new String[] {newFile.getRootPath()}),
                false);

            newResource = writeFile(dbc, newFile);
        } else {
            // it is a folder!
            newResource = new CmsFolder(
                resource.getStructureId(),
                resource.getResourceId(),
                resource.getRootPath(),
                historyResource.getTypeId(),
                historyResource.getFlags(),
                dbc.currentProject().getUuid(),
                state,
                resource.getDateCreated(),
                historyResource.getUserCreated(),
                resource.getDateLastModified(),
                dbc.currentUser().getId(),
                historyResource.getDateReleased(),
                historyResource.getDateExpired(),
                newVersion);

            // log it
            log(
                dbc,
                new CmsLogEntry(
                    dbc,
                    newResource.getStructureId(),
                    CmsLogEntryType.RESOURCE_HISTORY,
                    new String[] {newResource.getRootPath()}),
                false);

            writeResource(dbc, newResource);
        }
        if (newResource != null) {
            // now read the historical properties
            List<CmsProperty> historyProperties = getHistoryDriver(dbc).readProperties(dbc, historyResource);
            // remove all properties
            deleteAllProperties(dbc, newResource.getRootPath());
            // write them to the restored resource
            writePropertyObjects(dbc, newResource, historyProperties, false);

            m_monitor.clearResourceCache();
        }

        Map<String, Object> data = new HashMap<String, Object>(2);
        data.put(I_CmsEventListener.KEY_RESOURCE, resource);
        data.put(I_CmsEventListener.KEY_CHANGE, new Integer(CHANGED_RESOURCE | CHANGED_CONTENT));
        OpenCms.fireCmsEvent(new CmsEvent(I_CmsEventListener.EVENT_RESOURCE_MODIFIED, data));
    }

    /**
     * Saves a list of aliases for the same structure id, replacing any aliases for the same structure id.<p>
     *
     * @param dbc the current database context
     * @param project the current project
     * @param structureId the structure id for which the aliases should be saved
     * @param aliases the list of aliases to save
     *
     * @throws CmsException if something goes wrong
     */
    public void saveAliases(CmsDbContext dbc, CmsProject project, CmsUUID structureId, List<CmsAlias> aliases)
    throws CmsException {

        for (CmsAlias alias : aliases) {
            if (!structureId.equals(alias.getStructureId())) {
                throw new IllegalArgumentException("Aliases to replace must have the same structure id!");
            }
        }
        I_CmsVfsDriver vfsDriver = getVfsDriver(dbc);
        vfsDriver.deleteAliases(dbc, project, new CmsAliasFilter(null, null, structureId));
        for (CmsAlias alias : aliases) {
            String aliasPath = alias.getAliasPath();
            if (CmsAlias.ALIAS_PATTERN.matcher(aliasPath).matches()) {
                vfsDriver.insertAlias(dbc, project, alias);
            } else {
                LOG.error("Invalid alias path: " + aliasPath);
            }
        }
    }

    /**
     * Replaces the complete list of rewrite aliases for a given site root.<p>
     *
     * @param dbc the current database context
     * @param siteRoot the site root for which the rewrite aliases should be replaced
     * @param newAliases the new aliases for the given site root
     * @throws CmsException if something goes wrong
     */
    public void saveRewriteAliases(CmsDbContext dbc, String siteRoot, List<CmsRewriteAlias> newAliases)
    throws CmsException {

        CmsRewriteAliasFilter filter = new CmsRewriteAliasFilter().setSiteRoot(siteRoot);
        getVfsDriver(dbc).deleteRewriteAliases(dbc, filter);
        getVfsDriver(dbc).insertRewriteAliases(dbc, newAliases);
    }

    /**
     * Searches for users which fit the given criteria.<p>
     *
     * @param dbc the database context
     * @param searchParams the search criteria
     *
     * @return the users which fit the search criteria
     *
     * @throws CmsDataAccessException if something goes wrong
     */
    public List<CmsUser> searchUsers(CmsDbContext dbc, CmsUserSearchParameters searchParams

    ) throws CmsDataAccessException {

        return getUserDriver(dbc).searchUsers(dbc, searchParams);
    }

    /**
     * Changes the "expire" date of a resource.<p>
     *
     * @param dbc the current database context
     * @param resource the resource to touch
     * @param dateExpired the new expire date of the resource
     *
     * @throws CmsDataAccessException if something goes wrong
     *
     * @see CmsObject#setDateExpired(String, long, boolean)
     * @see I_CmsResourceType#setDateExpired(CmsObject, CmsSecurityManager, CmsResource, long, boolean)
     */
    public void setDateExpired(CmsDbContext dbc, CmsResource resource, long dateExpired) throws CmsDataAccessException {

        resource.setDateExpired(dateExpired);
        if (resource.getState().isUnchanged()) {
            resource.setState(CmsResource.STATE_CHANGED);
        }
        getVfsDriver(dbc).writeResourceState(dbc, dbc.currentProject(), resource, UPDATE_STRUCTURE, false);

        // modify the last modified project reference
        getVfsDriver(dbc).writeResourceState(dbc, dbc.currentProject(), resource, UPDATE_RESOURCE_PROJECT, false);
        // log
        log(
            dbc,
            new CmsLogEntry(
                dbc,
                resource.getStructureId(),
                CmsLogEntryType.RESOURCE_DATE_EXPIRED,
                new String[] {resource.getRootPath()}),
            false);

        // clear the cache
        m_monitor.clearResourceCache();

        // fire the event
        Map<String, Object> data = new HashMap<String, Object>(2);
        data.put(I_CmsEventListener.KEY_RESOURCE, resource);
        data.put(I_CmsEventListener.KEY_CHANGE, new Integer(CHANGED_TIMEFRAME));
        OpenCms.fireCmsEvent(new CmsEvent(I_CmsEventListener.EVENT_RESOURCE_MODIFIED, data));
    }

    /**
     * Changes the "last modified" timestamp of a resource.<p>
     *
     * @param dbc the current database context
     * @param resource the resource to touch
     * @param dateLastModified the new last modified date of the resource
     *
     * @throws CmsDataAccessException if something goes wrong
     *
     * @see CmsObject#setDateLastModified(String, long, boolean)
     * @see I_CmsResourceType#setDateLastModified(CmsObject, CmsSecurityManager, CmsResource, long, boolean)
     */
    public void setDateLastModified(CmsDbContext dbc, CmsResource resource, long dateLastModified)
    throws CmsDataAccessException {

        // modify the last modification date
        resource.setDateLastModified(dateLastModified);
        if (resource.getState().isUnchanged()) {
            resource.setState(CmsResource.STATE_CHANGED);
        } else if (resource.getState().isNew() && (resource.getSiblingCount() > 1)) {
            // in case of new resources with siblings make sure the state is correct
            resource.setState(CmsResource.STATE_CHANGED);
        }
        resource.setUserLastModified(dbc.currentUser().getId());
        getVfsDriver(dbc).writeResourceState(dbc, dbc.currentProject(), resource, UPDATE_RESOURCE, false);

        log(
            dbc,
            new CmsLogEntry(
                dbc,
                resource.getStructureId(),
                CmsLogEntryType.RESOURCE_TOUCHED,
                new String[] {resource.getRootPath()}),
            false);

        // clear the cache
        m_monitor.clearResourceCache();

        // fire the event
        Map<String, Object> data = new HashMap<String, Object>(2);
        data.put(I_CmsEventListener.KEY_RESOURCE, resource);
        data.put(I_CmsEventListener.KEY_CHANGE, new Integer(CHANGED_LASTMODIFIED));
        OpenCms.fireCmsEvent(new CmsEvent(I_CmsEventListener.EVENT_RESOURCE_MODIFIED, data));
    }

    /**
     * Changes the "release" date of a resource.<p>
     *
     * @param dbc the current database context
     * @param resource the resource to touch
     * @param dateReleased the new release date of the resource
     *
     * @throws CmsDataAccessException if something goes wrong
     *
     * @see CmsObject#setDateReleased(String, long, boolean)
     * @see I_CmsResourceType#setDateReleased(CmsObject, CmsSecurityManager, CmsResource, long, boolean)
     */
    public void setDateReleased(CmsDbContext dbc, CmsResource resource, long dateReleased)
    throws CmsDataAccessException {

        // modify the last modification date
        resource.setDateReleased(dateReleased);
        if (resource.getState().isUnchanged()) {
            resource.setState(CmsResource.STATE_CHANGED);
        }
        getVfsDriver(dbc).writeResourceState(dbc, dbc.currentProject(), resource, UPDATE_STRUCTURE, false);

        // modify the last modified project reference
        getVfsDriver(dbc).writeResourceState(dbc, dbc.currentProject(), resource, UPDATE_RESOURCE_PROJECT, false);
        // log it
        log(
            dbc,
            new CmsLogEntry(
                dbc,
                resource.getStructureId(),
                CmsLogEntryType.RESOURCE_DATE_RELEASED,
                new String[] {resource.getRootPath()}),
            false);

        // clear the cache
        m_monitor.clearResourceCache();

        // fire the event
        Map<String, Object> data = new HashMap<String, Object>(2);
        data.put(I_CmsEventListener.KEY_RESOURCE, resource);
        data.put(I_CmsEventListener.KEY_CHANGE, new Integer(CHANGED_TIMEFRAME));
        OpenCms.fireCmsEvent(new CmsEvent(I_CmsEventListener.EVENT_RESOURCE_MODIFIED, data));
    }

    /**
     * Sets a new parent group for an already existing group.<p>
     *
     * @param dbc the current database context
     * @param groupName the name of the group that should be written
     * @param parentGroupName the name of the parent group to set,
     *                      or <code>null</code> if the parent
     *                      group should be deleted.
     *
     * @throws CmsException if operation was not successful
     * @throws CmsDataAccessException if the group with <code>groupName</code> could not be read from VFS
     */
    public void setParentGroup(CmsDbContext dbc, String groupName, String parentGroupName)
    throws CmsException, CmsDataAccessException {

        CmsGroup group = readGroup(dbc, groupName);
        CmsUUID parentGroupId = CmsUUID.getNullUUID();

        // if the group exists, use its id, else set to unknown.
        if (parentGroupName != null) {
            parentGroupId = readGroup(dbc, parentGroupName).getId();
        }

        group.setParentId(parentGroupId);

        // write the changes to the cms
        writeGroup(dbc, group);
    }

    /**
     * Sets the password for a user.<p>
     *
     * @param dbc the current database context
     * @param username the name of the user
     * @param newPassword the new password
     *
     * @throws CmsException if operation was not successful
     * @throws CmsIllegalArgumentException if the user with the <code>username</code> was not found
     */
    public void setPassword(CmsDbContext dbc, String username, String newPassword)
    throws CmsException, CmsIllegalArgumentException {

        validatePassword(newPassword);

        // read the user as a system user to verify that the specified old password is correct
        CmsUser user = getUserDriver(dbc).readUser(dbc, username);
        // only continue if not found and read user from web might succeed
        getUserDriver(dbc).writePassword(dbc, username, null, newPassword);
        user.getAdditionalInfo().put(
            CmsUserSettings.ADDITIONAL_INFO_LAST_PASSWORD_CHANGE,
            "" + System.currentTimeMillis());
        getUserDriver(dbc).writeUser(dbc, user);

        // fire user modified event
        Map<String, Object> eventData = new HashMap<String, Object>();
        eventData.put(I_CmsEventListener.KEY_USER_ID, user.getId().toString());
        eventData.put(I_CmsEventListener.KEY_USER_ACTION, I_CmsEventListener.VALUE_USER_MODIFIED_ACTION_RESET_PASSWORD);
        eventData.put(
            I_CmsEventListener.KEY_USER_CHANGES,
            Integer.valueOf(CmsUser.FLAG_CORE_DATA | CmsUser.FLAG_CORE_DATA));
        OpenCms.fireCmsEvent(new CmsEvent(I_CmsEventListener.EVENT_USER_MODIFIED, eventData));
    }

    /**
     * Marks a subscribed resource as deleted.<p>
     *
     * @param dbc the database context
     * @param poolName the name of the database pool to use
     * @param resource the subscribed resource to mark as deleted
     *
     * @throws CmsException if something goes wrong
     */
    public void setSubscribedResourceAsDeleted(CmsDbContext dbc, String poolName, CmsResource resource)
    throws CmsException {

        getSubscriptionDriver().setSubscribedResourceAsDeleted(dbc, poolName, resource);
    }

    /**
     * Moves an user to the given organizational unit.<p>
     *
     * @param dbc the current db context
     * @param orgUnit the organizational unit to add the resource to
     * @param user the user that is to be moved to the organizational unit
     *
     * @throws CmsException if something goes wrong
     *
     * @see org.opencms.security.CmsOrgUnitManager#setUsersOrganizationalUnit(CmsObject, String, String)
     */
    public void setUsersOrganizationalUnit(CmsDbContext dbc, CmsOrganizationalUnit orgUnit, CmsUser user)
    throws CmsException {

        if (!getGroupsOfUser(dbc, user.getName(), false).isEmpty()) {
            throw new CmsDbConsistencyException(
                Messages.get().container(Messages.ERR_ORGUNIT_MOVE_USER_2, orgUnit.getName(), user.getName()));
        }

        // move the principal
        getUserDriver(dbc).setUsersOrganizationalUnit(dbc, orgUnit, user);
        // remove the principal from cache
        m_monitor.clearUserCache(user);

        if (!dbc.getProjectId().isNullUUID()) {
            // user modified event is not needed
            return;
        }
        // fire user modified event
        Map<String, Object> eventData = new HashMap<String, Object>();
        eventData.put(I_CmsEventListener.KEY_USER_ID, user.getId().toString());
        eventData.put(I_CmsEventListener.KEY_OU_NAME, user.getOuFqn());
        eventData.put(I_CmsEventListener.KEY_USER_ACTION, I_CmsEventListener.VALUE_USER_MODIFIED_ACTION_SET_OU);
        OpenCms.fireCmsEvent(new CmsEvent(I_CmsEventListener.EVENT_USER_MODIFIED, eventData));
    }

    /**
     * Subscribes the user or group to the resource.<p>
     *
     * @param dbc the database context
     * @param poolName the name of the database pool to use
     * @param principal the principal that subscribes to the resource
     * @param resource the resource to subscribe to
     *
     * @throws CmsException if something goes wrong
     */
    public void subscribeResourceFor(CmsDbContext dbc, String poolName, CmsPrincipal principal, CmsResource resource)
    throws CmsException {

        getSubscriptionDriver().subscribeResourceFor(dbc, poolName, principal, resource);
    }

    /**
     * Undelete the resource.<p>
     *
     * @param dbc the current database context
     * @param resource the name of the resource to apply this operation to
     *
     * @throws CmsException if something goes wrong
     *
     * @see CmsObject#undeleteResource(String, boolean)
     * @see I_CmsResourceType#undelete(CmsObject, CmsSecurityManager, CmsResource, boolean)
     */
    public void undelete(CmsDbContext dbc, CmsResource resource) throws CmsException {

        if (!resource.getState().isDeleted()) {
            throw new CmsVfsException(
                Messages.get().container(
                    Messages.ERR_UNDELETE_FOR_RESOURCE_DELETED_1,
                    dbc.removeSiteRoot(resource.getRootPath())));
        }

        // set the state to changed
        resource.setState(CmsResourceState.STATE_CHANGED);
        // perform the changes
        updateState(dbc, resource, false);
        // log it
        log(
            dbc,
            new CmsLogEntry(
                dbc,
                resource.getStructureId(),
                CmsLogEntryType.RESOURCE_UNDELETED,
                new String[] {resource.getRootPath()}),
            false);
        // clear the cache
        m_monitor.clearResourceCache();

        // fire change event
        Map<String, Object> data = new HashMap<String, Object>(2);
        data.put(I_CmsEventListener.KEY_RESOURCE, resource);
        data.put(I_CmsEventListener.KEY_CHANGE, new Integer(CHANGED_RESOURCE));
        OpenCms.fireCmsEvent(new CmsEvent(I_CmsEventListener.EVENT_RESOURCE_MODIFIED, data));
    }

    /**
     * Undos all changes in the resource by restoring the version from the
     * online project to the current offline project.<p>
     *
     * @param dbc the current database context
     * @param resource the name of the resource to apply this operation to
     * @param mode the undo mode, one of the <code>{@link org.opencms.file.CmsResource.CmsResourceUndoMode}#UNDO_XXX</code> constants
     *      please note that the recursive flag is ignored at this level
     *
     * @throws CmsException if something goes wrong
     *
     * @see CmsObject#undoChanges(String, CmsResource.CmsResourceUndoMode)
     * @see I_CmsResourceType#undoChanges(CmsObject, CmsSecurityManager, CmsResource, CmsResource.CmsResourceUndoMode)
     */
    public void undoChanges(CmsDbContext dbc, CmsResource resource, CmsResource.CmsResourceUndoMode mode)
    throws CmsException {

        if (resource.getState().isNew()) {
            // undo changes is impossible on a new resource
            throw new CmsVfsException(Messages.get().container(Messages.ERR_UNDO_CHANGES_FOR_RESOURCE_NEW_0));
        }

        // we need this for later use
        CmsProject onlineProject = readProject(dbc, CmsProject.ONLINE_PROJECT_ID);
        // read the resource from the online project
        CmsResource onlineResource = getVfsDriver(
            dbc).readResource(dbc, CmsProject.ONLINE_PROJECT_ID, resource.getStructureId(), true);

        CmsResource onlineResourceByPath = null;
        try {
            // this is needed to figure out if a moved resource overwrote a deleted one
            onlineResourceByPath = getVfsDriver(
                dbc).readResource(dbc, CmsProject.ONLINE_PROJECT_ID, resource.getRootPath(), true);

            // force undo move operation if needed
            if (!mode.isUndoMove() && !onlineResourceByPath.getRootPath().equals(onlineResource.getRootPath())) {
                mode = mode.includeMove();
            }
        } catch (Exception e) {
            // ok
        }

        boolean moved = !onlineResource.getRootPath().equals(resource.getRootPath());
        // undo move operation if required
        if (moved && mode.isUndoMove()) {
            moveResource(dbc, resource, onlineResource.getRootPath(), true);
            if ((onlineResourceByPath != null)
                && !onlineResourceByPath.getRootPath().equals(onlineResource.getRootPath())) {
                // was moved over deleted, so the deleted file has to be undone
                undoContentChanges(dbc, onlineProject, null, onlineResourceByPath, CmsResource.STATE_UNCHANGED, true);
            }
        }
        // undo content changes
        CmsResourceState newState = CmsResource.STATE_UNCHANGED;
        if (moved && !mode.isUndoMove()) {
            newState = CmsResource.STATE_CHANGED;
        }
        undoContentChanges(dbc, onlineProject, resource, onlineResource, newState, moved && mode.isUndoMove());
        // because undoContentChanges deletes the offline resource internally, we have
        // to write an entry to the log table to prevent the resource from appearing in the
        // user's publish list.
        log(
            dbc,
            new CmsLogEntry(
                dbc,
                resource.getStructureId(),
                CmsLogEntryType.RESOURCE_CHANGES_UNDONE,
                new String[] {resource.getRootPath()}),
            true);

    }

    /**
     * Unlocks all resources in the given project.<p>
     *
     * @param project the project to unlock the resources in
     */
    public void unlockProject(CmsProject project) {

        // unlock all resources in the project
        m_lockManager.removeResourcesInProject(project.getUuid(), false);
        m_monitor.clearResourceCache();
        m_monitor.flushCache(CmsMemoryMonitor.CacheType.PROJECT, CmsMemoryMonitor.CacheType.PERMISSION);
    }

    /**
     * Unlocks a resource.<p>
     *
     * @param dbc the current database context
     * @param resource the resource to unlock
     * @param force <code>true</code>, if a resource is forced to get unlocked, no matter by which user and in which project the resource is currently locked
     * @param removeSystemLock <code>true</code>, if you also want to remove system locks
     *
     * @throws CmsException if something goes wrong
     *
     * @see CmsObject#unlockResource(String)
     * @see I_CmsResourceType#unlockResource(CmsObject, CmsSecurityManager, CmsResource)
     */
    public void unlockResource(CmsDbContext dbc, CmsResource resource, boolean force, boolean removeSystemLock)
    throws CmsException {

        // update the resource cache
        m_monitor.clearResourceCache();

        // now update lock status
        m_lockManager.removeResource(dbc, resource, force, removeSystemLock);

        // we must also clear the permission cache
        m_monitor.flushCache(CmsMemoryMonitor.CacheType.PERMISSION);

        // fire resource modification event
        Map<String, Object> data = new HashMap<String, Object>(2);
        data.put(I_CmsEventListener.KEY_RESOURCE, resource);
        data.put(I_CmsEventListener.KEY_CHANGE, new Integer(NOTHING_CHANGED));
        OpenCms.fireCmsEvent(new CmsEvent(I_CmsEventListener.EVENT_RESOURCE_MODIFIED, data));
    }

    /**
     * Unsubscribes all deleted resources that were deleted before the specified time stamp.<p>
     *
     * @param dbc the database context
     * @param poolName the name of the database pool to use
     * @param deletedTo the time stamp to which the resources have been deleted
     *
     * @throws CmsException if something goes wrong
     */
    public void unsubscribeAllDeletedResources(CmsDbContext dbc, String poolName, long deletedTo) throws CmsException {

        getSubscriptionDriver().unsubscribeAllDeletedResources(dbc, poolName, deletedTo);
    }

    /**
     * Unsubscribes the principal from all resources.<p>
     *
     * @param dbc the database context
     * @param poolName the name of the database pool to use
     * @param principal the principal that unsubscribes from all resources
     *
     * @throws CmsException if something goes wrong
     */
    public void unsubscribeAllResourcesFor(CmsDbContext dbc, String poolName, CmsPrincipal principal)
    throws CmsException {

        getSubscriptionDriver().unsubscribeAllResourcesFor(dbc, poolName, principal);

    }

    /**
     * Unsubscribes the principal from the resource.<p>
     *
     * @param dbc the database context
     * @param poolName the name of the database pool to use
     * @param principal the principal that unsubscribes from the resource
     * @param resource the resource to unsubscribe from
     *
     * @throws CmsException if something goes wrong
     */
    public void unsubscribeResourceFor(CmsDbContext dbc, String poolName, CmsPrincipal principal, CmsResource resource)
    throws CmsException {

        getSubscriptionDriver().unsubscribeResourceFor(dbc, poolName, principal, resource);
    }

    /**
     * Unsubscribes all groups and users from the resource.<p>
     *
     * @param dbc the database context
     * @param poolName the name of the database pool to use
     * @param resource the resource to unsubscribe all groups and users from
     *
     * @throws CmsException if something goes wrong
     */
    public void unsubscribeResourceForAll(CmsDbContext dbc, String poolName, CmsResource resource) throws CmsException {

        getSubscriptionDriver().unsubscribeResourceForAll(dbc, poolName, resource);
    }

    /**
     * Update the export points.<p>
     *
     * All files and folders "inside" an export point are written.<p>
     *
     * @param dbc the current database context
     */
    public void updateExportPoints(CmsDbContext dbc) {

        try {
            // read the export points and return immediately if there are no export points at all
            Set<CmsExportPoint> exportPoints = new HashSet<CmsExportPoint>();
            exportPoints.addAll(OpenCms.getExportPoints());
            exportPoints.addAll(OpenCms.getModuleManager().getExportPoints());
            if (exportPoints.size() == 0) {
                if (LOG.isWarnEnabled()) {
                    LOG.warn(Messages.get().getBundle().key(Messages.LOG_NO_EXPORT_POINTS_CONFIGURED_0));
                }
                return;
            }

            // create the driver to write the export points
            I_CmsExportPointDriver exportPointDriver = OpenCms.getImportExportManager().createExportPointDriver(
                exportPoints);

            // the export point hash table contains RFS export paths keyed by their internal VFS paths
            Iterator<String> i = exportPointDriver.getExportPointPaths().iterator();
            I_CmsVfsDriver vfsDriver = getVfsDriver(dbc);
            while (i.hasNext()) {
                String currentExportPoint = i.next();

                // print some report messages
                if (LOG.isInfoEnabled()) {
                    LOG.info(Messages.get().getBundle().key(Messages.LOG_WRITE_EXPORT_POINT_1, currentExportPoint));
                }

                try {
                    CmsResourceFilter filter = CmsResourceFilter.DEFAULT;
                    List<CmsResource> resources = vfsDriver.readResourceTree(
                        dbc,
                        CmsProject.ONLINE_PROJECT_ID,
                        currentExportPoint,
                        filter.getType(),
                        filter.getState(),
                        filter.getModifiedAfter(),
                        filter.getModifiedBefore(),
                        filter.getReleaseAfter(),
                        filter.getReleaseBefore(),
                        filter.getExpireAfter(),
                        filter.getExpireBefore(),
                        CmsDriverManager.READMODE_INCLUDE_TREE
                            | (filter.excludeType() ? CmsDriverManager.READMODE_EXCLUDE_TYPE : 0)
                            | (filter.excludeState() ? CmsDriverManager.READMODE_EXCLUDE_STATE : 0));

                    Iterator<CmsResource> j = resources.iterator();
                    while (j.hasNext()) {
                        CmsResource currentResource = j.next();

                        if (currentResource.isFolder()) {
                            // export the folder
                            exportPointDriver.createFolder(currentResource.getRootPath(), currentExportPoint);
                        } else {
                            // try to create the exportpoint folder
                            exportPointDriver.createFolder(currentExportPoint, currentExportPoint);
                            byte[] onlineContent = vfsDriver.readContent(
                                dbc,
                                CmsProject.ONLINE_PROJECT_ID,
                                currentResource.getResourceId());
                            // export the file content online
                            exportPointDriver.writeFile(
                                currentResource.getRootPath(),
                                currentExportPoint,
                                onlineContent);
                        }
                    }
                } catch (CmsException e) {
                    // there might exist export points without corresponding resources in the VFS
                    // -> ignore exceptions which are not "resource not found" exception quiet here
                    if (e instanceof CmsVfsResourceNotFoundException) {
                        if (LOG.isErrorEnabled()) {
                            LOG.error(Messages.get().getBundle().key(Messages.LOG_UPDATE_EXORT_POINTS_ERROR_0), e);
                        }
                    }
                }
            }
        } catch (Exception e) {
            if (LOG.isErrorEnabled()) {
                LOG.error(Messages.get().getBundle().key(Messages.LOG_UPDATE_EXORT_POINTS_ERROR_0), e);
            }
        }
    }

    /**
     * Updates the last login date on the given user to the current time.<p>
     *
     * @param dbc the current database context
     * @param user the user to be updated
     *
     * @throws CmsException if operation was not successful
     */
    public void updateLastLoginDate(CmsDbContext dbc, CmsUser user) throws CmsException {

        m_monitor.clearUserCache(user);
        // set the last login time to the current time
        user.setLastlogin(System.currentTimeMillis());
        dbc.setAttribute(ATTRIBUTE_LOGIN, user.getName());
        getUserDriver(dbc).writeUser(dbc, user);
        // update cache
        m_monitor.cacheUser(user);

        // invalidate all user dependent caches
        m_monitor.flushCache(
            CmsMemoryMonitor.CacheType.ACL,
            CmsMemoryMonitor.CacheType.GROUP,
            CmsMemoryMonitor.CacheType.ORG_UNIT,
            CmsMemoryMonitor.CacheType.USERGROUPS,
            CmsMemoryMonitor.CacheType.USER_LIST,
            CmsMemoryMonitor.CacheType.PERMISSION,
            CmsMemoryMonitor.CacheType.RESOURCE_LIST);

        // fire user modified event
        Map<String, Object> eventData = new HashMap<String, Object>();
        eventData.put(I_CmsEventListener.KEY_USER_ID, user.getId().toString());
        eventData.put(I_CmsEventListener.KEY_USER_NAME, user.getName());
        eventData.put(I_CmsEventListener.KEY_USER_ACTION, I_CmsEventListener.VALUE_USER_MODIFIED_ACTION_WRITE_USER);
        eventData.put(I_CmsEventListener.KEY_USER_CHANGES, Integer.valueOf(CmsUser.FLAG_LAST_LOGIN));
        OpenCms.fireCmsEvent(new CmsEvent(I_CmsEventListener.EVENT_USER_MODIFIED, eventData));
    }

    /**
     * Logs everything that has not been written to DB jet.<p>
     *
     * @param dbc the current db context
     *
     * @throws CmsDataAccessException if something goes wrong
     */
    public void updateLog(CmsDbContext dbc) throws CmsDataAccessException {

        synchronized (m_publishListUpdateLock) {

            if (m_log.isEmpty()) {
                return;
            }

            List<CmsLogEntry> log = new ArrayList<CmsLogEntry>(m_log);
            m_log.clear();
            String logTableEnabledStr = (String)OpenCms.getRuntimeProperty(PARAM_LOG_TABLE_ENABLED);
            if (Boolean.parseBoolean(logTableEnabledStr)) { // defaults to 'false' if value not set
                m_projectDriver.log(dbc, log);
            }
            A_CmsLogPublishListConverter converter = null;
            switch (OpenCms.getPublishManager().getPublishListRemoveMode()) {
                case currentUser:
                    converter = new CmsLogPublishListConverterCurrentUser();
                    break;
                case allUsers:
                default:
                    converter = new CmsLogPublishListConverterAllUsers();
                    break;
            }
            for (CmsLogEntry entry : log) {
                converter.add(entry);
            }
            converter.writeChangesToDatabase(dbc, m_projectDriver);
        }
    }

    /**
     * Updates/Creates the given relations for the given resource.<p>
     *
     * @param dbc the db context
     * @param resource the resource to update the relations for
     * @param links the links to consider for updating
     *
     * @throws CmsException if something goes wrong
     *
     * @see CmsSecurityManager#updateRelationsForResource(CmsRequestContext, CmsResource, List)
     */
    public void updateRelationsForResource(CmsDbContext dbc, CmsResource resource, List<CmsLink> links)
    throws CmsException {

        deleteRelationsWithSiblings(dbc, resource);

        // build the links again only if needed
        if ((links == null) || links.isEmpty()) {
            return;
        }
        // the set of written relations
        Set<CmsRelation> writtenRelations = new HashSet<CmsRelation>();

        // create new relation information
        I_CmsVfsDriver vfsDriver = getVfsDriver(dbc);
        Iterator<CmsLink> itLinks = links.iterator();
        while (itLinks.hasNext()) {
            CmsLink link = itLinks.next();
            if (link.isInternal()) { // only update internal links
                if (CmsStringUtil.isEmptyOrWhitespaceOnly(link.getTarget())) {
                    // only an anchor
                    continue;
                }
                CmsUUID targetId = link.getStructureId();
                String destPath = link.getTarget();

                if (targetId != null) {
                    // the link target may not be a VFS path even if the link id is a structure id,
                    // so if possible, we read the resource for the id and set the relation target to its
                    // real root path.
                    try {
                        CmsResource destRes = readResource(dbc, targetId, CmsResourceFilter.ALL);
                        destPath = destRes.getRootPath();
                    } catch (CmsVfsResourceNotFoundException e) {
                        // ignore
                    }
                }

                CmsRelation originalRelation = new CmsRelation(
                    resource.getStructureId(),
                    resource.getRootPath(),
                    link.getStructureId(),
                    destPath,
                    link.getType());

                // do not write twice the same relation
                if (writtenRelations.contains(originalRelation)) {
                    continue;
                }
                writtenRelations.add(originalRelation);

                // TODO: it would be good to have the link locale to make the relation just to the right sibling
                // create the relations in content for all siblings
                Iterator<CmsResource> itSiblings = readSiblings(dbc, resource, CmsResourceFilter.ALL).iterator();
                while (itSiblings.hasNext()) {
                    CmsResource sibling = itSiblings.next();
                    CmsRelation relation = new CmsRelation(
                        sibling.getStructureId(),
                        sibling.getRootPath(),
                        originalRelation.getTargetId(),
                        originalRelation.getTargetPath(),
                        link.getType());
                    vfsDriver.createRelation(dbc, dbc.currentProject().getUuid(), relation);
                }
            }
        }
    }

    /**
     * Returns <code>true</code> if a user is member of the given group.<p>
     *
     * @param dbc the current database context
     * @param username the name of the user to check
     * @param groupname the name of the group to check
     * @param readRoles if to read roles or groups
     *
     * @return <code>true</code>, if the user is in the group, <code>false</code> otherwise
     *
     * @throws CmsException if something goes wrong
     */
    public boolean userInGroup(CmsDbContext dbc, String username, String groupname, boolean readRoles)
    throws CmsException {

        List<CmsGroup> groups = getGroupsOfUser(dbc, username, readRoles);
        for (int i = 0; i < groups.size(); i++) {
            CmsGroup group = groups.get(i);
            if (groupname.equals(group.getName()) || groupname.substring(1).equals(group.getName())) {
                return true;
            }
        }
        return false;
    }

    /**
     * This method checks if a new password follows the rules for
     * new passwords, which are defined by a Class implementing the
     * <code>{@link org.opencms.security.I_CmsPasswordHandler}</code>
     * interface and configured in the opencms.properties file.<p>
     *
     * If this method throws no exception the password is valid.<p>
     *
     * @param password the new password that has to be checked
     *
     * @throws CmsSecurityException if the password is not valid
     */
    public void validatePassword(String password) throws CmsSecurityException {

        OpenCms.getPasswordHandler().validatePassword(password);
    }

    /**
     * Validates the relations for the given resources.<p>
     *
     * @param dbc the database context
     * @param publishList the resources to validate during publishing
     * @param report a report to write the messages to
     *
     * @return a map with lists of invalid links
     *          (<code>{@link org.opencms.relations.CmsRelation}}</code> objects)
     *          keyed by root paths
     *
     * @throws Exception if something goes wrong
     */
    public Map<String, List<CmsRelation>> validateRelations(
        CmsDbContext dbc,
        CmsPublishList publishList,
        I_CmsReport report)
    throws Exception {

        return m_htmlLinkValidator.validateResources(dbc, publishList, report);
    }

    /**
     * Writes an access control entries to a given resource.<p>
     *
     * @param dbc the current database context
     * @param resource the resource
     * @param ace the entry to write
     *
     * @throws CmsException if something goes wrong
     */
    public void writeAccessControlEntry(CmsDbContext dbc, CmsResource resource, CmsAccessControlEntry ace)
    throws CmsException {

        // write the new ace
        getUserDriver(dbc).writeAccessControlEntry(dbc, dbc.currentProject(), ace);

        // log it
        log(
            dbc,
            new CmsLogEntry(
                dbc,
                resource.getStructureId(),
                CmsLogEntryType.RESOURCE_PERMISSIONS,
                new String[] {resource.getRootPath()}),
            false);

        // update the "last modified" information
        setDateLastModified(dbc, resource, resource.getDateLastModified());

        // clear the cache
        m_monitor.clearAccessControlListCache();

        // fire a resource modification event
        Map<String, Object> data = new HashMap<String, Object>(2);
        data.put(I_CmsEventListener.KEY_RESOURCE, resource);
        data.put(I_CmsEventListener.KEY_CHANGE, new Integer(CHANGED_ACCESSCONTROL));
        OpenCms.fireCmsEvent(new CmsEvent(I_CmsEventListener.EVENT_RESOURCE_MODIFIED, data));
    }

    /**
     * Writes all export points into the file system for the publish task
     * specified by trhe given publish history ID.<p>
     *
     * @param dbc the current database context
     * @param report an I_CmsReport instance to print output message, or null to write messages to the log file
     * @param publishHistoryId ID to identify the publish task in the publish history
     */
    public void writeExportPoints(CmsDbContext dbc, I_CmsReport report, CmsUUID publishHistoryId) {

        boolean printReportHeaders = false;
        List<CmsPublishedResource> publishedResources = null;
        try {
            // read the "published resources" for the specified publish history ID
            publishedResources = getProjectDriver(dbc).readPublishedResources(dbc, publishHistoryId);
        } catch (CmsException e) {
            if (LOG.isErrorEnabled()) {
                LOG.error(
                    Messages.get().getBundle().key(Messages.ERR_READ_PUBLISHED_RESOURCES_FOR_ID_1, publishHistoryId),
                    e);
            }
        }
        if ((publishedResources == null) || publishedResources.isEmpty()) {
            if (LOG.isWarnEnabled()) {
                LOG.warn(Messages.get().getBundle().key(Messages.LOG_EMPTY_PUBLISH_HISTORY_1, publishHistoryId));
            }
            return;
        }

        // read the export points and return immediately if there are no export points at all
        Set<CmsExportPoint> exportPoints = new HashSet<CmsExportPoint>();
        exportPoints.addAll(OpenCms.getExportPoints());
        exportPoints.addAll(OpenCms.getModuleManager().getExportPoints());
        if (exportPoints.size() == 0) {
            if (LOG.isWarnEnabled()) {
                LOG.warn(Messages.get().getBundle().key(Messages.LOG_NO_EXPORT_POINTS_CONFIGURED_0));
            }
            return;
        }

        // create the driver to write the export points
        I_CmsExportPointDriver exportPointDriver = OpenCms.getImportExportManager().createExportPointDriver(
            exportPoints);

        // the report may be null if the export point write was started by an event
        if (report == null) {
            if (dbc.getRequestContext() != null) {
                report = new CmsLogReport(dbc.getRequestContext().getLocale(), getClass());
            } else {
                report = new CmsLogReport(CmsLocaleManager.getDefaultLocale(), getClass());
            }
        }

        // iterate over all published resources to export them
        I_CmsVfsDriver vfsDriver = getVfsDriver(dbc);
        Iterator<CmsPublishedResource> i = publishedResources.iterator();
        while (i.hasNext()) {
            CmsPublishedResource currentPublishedResource = i.next();
            String currentExportPoint = exportPointDriver.getExportPoint(currentPublishedResource.getRootPath());

            if (currentExportPoint != null) {
                if (!printReportHeaders) {
                    report.println(
                        Messages.get().container(Messages.RPT_EXPORT_POINTS_WRITE_BEGIN_0),
                        I_CmsReport.FORMAT_HEADLINE);
                    printReportHeaders = true;
                }

                // print report message
                if (currentPublishedResource.getState().isDeleted()) {
                    report.print(
                        Messages.get().container(Messages.RPT_EXPORT_POINTS_DELETE_0),
                        I_CmsReport.FORMAT_NOTE);
                } else {
                    report.print(Messages.get().container(Messages.RPT_EXPORT_POINTS_WRITE_0), I_CmsReport.FORMAT_NOTE);
                }
                report.print(
                    org.opencms.report.Messages.get().container(
                        org.opencms.report.Messages.RPT_ARGUMENT_1,
                        currentPublishedResource.getRootPath()));
                report.print(org.opencms.report.Messages.get().container(org.opencms.report.Messages.RPT_DOTS_0));

                if (currentPublishedResource.isFolder()) {
                    // export the folder
                    if (currentPublishedResource.getState().isDeleted()) {
                        exportPointDriver.deleteResource(currentPublishedResource.getRootPath(), currentExportPoint);
                    } else {
                        exportPointDriver.createFolder(currentPublishedResource.getRootPath(), currentExportPoint);
                    }
                    report.println(
                        org.opencms.report.Messages.get().container(org.opencms.report.Messages.RPT_OK_0),
                        I_CmsReport.FORMAT_OK);
                } else {
                    // export the file
                    try {
                        if (currentPublishedResource.getState().isDeleted()) {
                            exportPointDriver.deleteResource(
                                currentPublishedResource.getRootPath(),
                                currentExportPoint);
                        } else {
                            // read the file content online
                            byte[] onlineContent = vfsDriver.readContent(
                                dbc,
                                CmsProject.ONLINE_PROJECT_ID,
                                currentPublishedResource.getResourceId());
                            exportPointDriver.writeFile(
                                currentPublishedResource.getRootPath(),
                                currentExportPoint,
                                onlineContent);
                        }
                        report.println(
                            org.opencms.report.Messages.get().container(org.opencms.report.Messages.RPT_OK_0),
                            I_CmsReport.FORMAT_OK);
                    } catch (CmsException e) {
                        if (LOG.isErrorEnabled()) {
                            LOG.error(
                                Messages.get().getBundle().key(
                                    Messages.LOG_WRITE_EXPORT_POINT_ERROR_1,
                                    currentPublishedResource.getRootPath()),
                                e);
                        }
                        report.println(
                            org.opencms.report.Messages.get().container(org.opencms.report.Messages.RPT_FAILED_0),
                            I_CmsReport.FORMAT_ERROR);
                    }
                }
            }
        }
        if (printReportHeaders) {
            report.println(
                Messages.get().container(Messages.RPT_EXPORT_POINTS_WRITE_END_0),
                I_CmsReport.FORMAT_HEADLINE);
        }
    }

    /**
     * Writes a resource to the OpenCms VFS, including it's content.<p>
     *
     * Applies only to resources of type <code>{@link CmsFile}</code>
     * i.e. resources that have a binary content attached.<p>
     *
     * Certain resource types might apply content validation or transformation rules
     * before the resource is actually written to the VFS. The returned result
     * might therefore be a modified version from the provided original.<p>
     *
     * @param dbc the current database context
     * @param resource the resource to apply this operation to
     *
     * @return the written resource (may have been modified)
     *
     * @throws CmsException if something goes wrong
     *
     * @see CmsObject#writeFile(CmsFile)
     * @see I_CmsResourceType#writeFile(CmsObject, CmsSecurityManager, CmsFile)
     */
    public CmsFile writeFile(CmsDbContext dbc, CmsFile resource) throws CmsException {

        resource.setUserLastModified(dbc.currentUser().getId());
        resource.setContents(resource.getContents()); // to be sure the content date is updated

        getVfsDriver(dbc).writeResource(dbc, dbc.currentProject().getUuid(), resource, UPDATE_RESOURCE_STATE);

        byte[] contents = resource.getContents();
        getVfsDriver(dbc).writeContent(dbc, resource.getResourceId(), contents);
        // log it
        log(
            dbc,
            new CmsLogEntry(
                dbc,
                resource.getStructureId(),
                CmsLogEntryType.RESOURCE_CONTENT_MODIFIED,
                new String[] {resource.getRootPath()}),
            false);

        // read the file back from db
        resource = new CmsFile(readResource(dbc, resource.getStructureId(), CmsResourceFilter.ALL));
        resource.setContents(contents);

        deleteRelationsWithSiblings(dbc, resource);

        // update the cache
        m_monitor.clearResourceCache();

        Map<String, Object> data = new HashMap<String, Object>(2);
        data.put(I_CmsEventListener.KEY_RESOURCE, resource);
        data.put(I_CmsEventListener.KEY_CHANGE, new Integer(CHANGED_CONTENT));
        OpenCms.fireCmsEvent(new CmsEvent(I_CmsEventListener.EVENT_RESOURCE_MODIFIED, data));

        return resource;
    }

    /**
     * Writes an already existing group.<p>
     *
     * The group id has to be a valid OpenCms group id.<br>
     *
     * The group with the given id will be completely overridden
     * by the given data.<p>
     *
     * @param dbc the current database context
     * @param group the group that should be written
     *
     * @throws CmsException if operation was not successful
     */
    public void writeGroup(CmsDbContext dbc, CmsGroup group) throws CmsException {

        CmsGroup oldGroup = readGroup(dbc, group.getName());
        m_monitor.uncacheGroup(oldGroup);
        getUserDriver(dbc).writeGroup(dbc, group);
        m_monitor.cacheGroup(group);

        if (!dbc.getProjectId().isNullUUID()) {
            // group modified event is not needed
            return;
        }
        // fire group modified event
        Map<String, Object> eventData = new HashMap<String, Object>();
        eventData.put(I_CmsEventListener.KEY_GROUP_ID, group.getId().toString());
        eventData.put(I_CmsEventListener.KEY_GROUP_NAME, oldGroup.getName());
        eventData.put(I_CmsEventListener.KEY_USER_ACTION, I_CmsEventListener.VALUE_GROUP_MODIFIED_ACTION_WRITE);
        OpenCms.fireCmsEvent(new CmsEvent(I_CmsEventListener.EVENT_GROUP_MODIFIED, eventData));
    }

    /**
     * Creates an historical entry of the current project.<p>
     *
     * @param dbc the current database context
     * @param publishTag the version
     * @param publishDate the date of publishing
     *
     * @throws CmsDataAccessException if operation was not successful
     */
    public void writeHistoryProject(CmsDbContext dbc, int publishTag, long publishDate) throws CmsDataAccessException {

        getHistoryDriver(dbc).writeProject(dbc, publishTag, publishDate);
    }

    /**
     * Writes the locks that are currently stored in-memory to the database to allow restoring them
     * in future server startups.<p>
     *
     * This overwrites the locks previously stored in the underlying database table.<p>
     *
     * @param dbc the current database context
     *
     * @throws CmsException if something goes wrong
     */
    public void writeLocks(CmsDbContext dbc) throws CmsException {

        m_lockManager.writeLocks(dbc);
    }

    /**
     * Writes an already existing organizational unit.<p>
     *
     * The organizational unit id has to be a valid OpenCms organizational unit id.<br>
     *
     * The organizational unit with the given id will be completely overridden
     * by the given data.<p>
     *
     * @param dbc the current db context
     * @param organizationalUnit the organizational unit that should be written
     *
     * @throws CmsException if operation was not successful
     *
     * @see org.opencms.security.CmsOrgUnitManager#writeOrganizationalUnit(CmsObject, CmsOrganizationalUnit)
     */
    public void writeOrganizationalUnit(CmsDbContext dbc, CmsOrganizationalUnit organizationalUnit)
    throws CmsException {

        m_monitor.uncacheOrgUnit(organizationalUnit);
        getUserDriver(dbc).writeOrganizationalUnit(dbc, organizationalUnit);

        // create a publish list for the 'virtual' publish event
        CmsResource ouRes = readResource(dbc, organizationalUnit.getId(), CmsResourceFilter.DEFAULT);
        CmsPublishList pl = new CmsPublishList(ouRes, false);
        pl.add(ouRes, false);

        getProjectDriver(dbc).writePublishHistory(
            dbc,
            pl.getPublishHistoryId(),
            new CmsPublishedResource(ouRes, -1, CmsResourceState.STATE_NEW));

        // fire the 'virtual' publish event
        Map<String, Object> eventData = new HashMap<String, Object>();
        eventData.put(I_CmsEventListener.KEY_PUBLISHID, pl.getPublishHistoryId().toString());
        eventData.put(I_CmsEventListener.KEY_PROJECTID, dbc.currentProject().getUuid());
        eventData.put(I_CmsEventListener.KEY_DBCONTEXT, dbc);
        CmsEvent afterPublishEvent = new CmsEvent(I_CmsEventListener.EVENT_PUBLISH_PROJECT, eventData);
        OpenCms.fireCmsEvent(afterPublishEvent);

        m_monitor.cacheOrgUnit(organizationalUnit);
    }

    /**
     * Writes an already existing project.<p>
     *
     * The project id has to be a valid OpenCms project id.<br>
     *
     * The project with the given id will be completely overridden
     * by the given data.<p>
     *
     * @param dbc the current database context
     * @param project the project that should be written
     *
     * @throws CmsException if operation was not successful
     */
    public void writeProject(CmsDbContext dbc, CmsProject project) throws CmsException {

        m_monitor.uncacheProject(project);
        getProjectDriver(dbc).writeProject(dbc, project);
        m_monitor.cacheProject(project);
    }

    /**
     * Writes a new project into the PROJECT_LASTMODIFIED field of a resource record.<p>
     *
     * @param dbc the current database context
     * @param resource the resource which should be modified
     * @param projectId the project id to write
     *
     * @throws CmsDataAccessException if the database access fails
     */
    public void writeProjectLastModified(CmsDbContext dbc, CmsResource resource, CmsUUID projectId)
    throws CmsDataAccessException {

        I_CmsVfsDriver vfsDriver = getVfsDriver(dbc);
        vfsDriver.writeLastModifiedProjectId(dbc, dbc.currentProject(), projectId, resource);
    }

    /**
     * Writes a property for a specified resource.<p>
     *
     * @param dbc the current database context
     * @param resource the resource to write the property for
     * @param property the property to write
     *
     * @throws CmsException if something goes wrong
     *
     * @see CmsObject#writePropertyObject(String, CmsProperty)
     * @see I_CmsResourceType#writePropertyObject(CmsObject, CmsSecurityManager, CmsResource, CmsProperty)
     */
    public void writePropertyObject(CmsDbContext dbc, CmsResource resource, CmsProperty property) throws CmsException {

        try {
            if (property == CmsProperty.getNullProperty()) {
                // skip empty or null properties
                return;
            }

            // test if and what state should be updated
            // 0: none, 1: structure, 2: resource
            int updateState = getUpdateState(dbc, resource, Collections.singletonList(property));

            // write the property
            getVfsDriver(dbc).writePropertyObject(dbc, dbc.currentProject(), resource, property);

            if (updateState > 0) {
                updateState(dbc, resource, updateState == 2);
            }
            // log it
            log(
                dbc,
                new CmsLogEntry(
                    dbc,
                    resource.getStructureId(),
                    CmsLogEntryType.RESOURCE_PROPERTIES,
                    new String[] {resource.getRootPath()}),
                false);

        } finally {
            // update the driver manager cache
            m_monitor.clearResourceCache();
            m_monitor.flushCache(CmsMemoryMonitor.CacheType.PROPERTY, CmsMemoryMonitor.CacheType.PROPERTY_LIST);

            // fire an event that a property of a resource has been modified
            Map<String, Object> data = new HashMap<String, Object>();
            data.put(I_CmsEventListener.KEY_RESOURCE, resource);
            data.put("property", property);
            OpenCms.fireCmsEvent(new CmsEvent(I_CmsEventListener.EVENT_PROPERTY_MODIFIED, data));
        }
    }

    /**
     * Writes a list of properties for a specified resource.<p>
     *
     * Code calling this method has to ensure that the no properties
     * <code>a, b</code> are contained in the specified list so that <code>a.equals(b)</code>,
     * otherwise an exception is thrown.<p>
     *
     * @param dbc the current database context
     * @param resource the resource to write the properties for
     * @param properties the list of properties to write
     * @param updateState if <code>true</code> the state of the resource will be updated
     *
     * @throws CmsException if something goes wrong
     *
     * @see CmsObject#writePropertyObjects(String, List)
     * @see I_CmsResourceType#writePropertyObjects(CmsObject, CmsSecurityManager, CmsResource, List)
     */
    public void writePropertyObjects(
        CmsDbContext dbc,
        CmsResource resource,
        List<CmsProperty> properties,
        boolean updateState)
    throws CmsException {

        if ((properties == null) || (properties.size() == 0)) {
            // skip empty or null lists
            return;
        }

        try {
            // the specified list must not contain two or more equal property objects
            for (int i = 0, n = properties.size(); i < n; i++) {
                Set<String> keyValidationSet = new HashSet<String>();
                CmsProperty property = properties.get(i);
                if (!keyValidationSet.contains(property.getName())) {
                    keyValidationSet.add(property.getName());
                } else {
                    throw new CmsVfsException(
                        Messages.get().container(Messages.ERR_VFS_INVALID_PROPERTY_LIST_1, property.getName()));
                }
            }

            // test if and what state should be updated
            // 0: none, 1: structure, 2: resource
            int updateStateValue = 0;
            if (updateState) {
                updateStateValue = getUpdateState(dbc, resource, properties);
            }
            I_CmsVfsDriver vfsDriver = getVfsDriver(dbc);
            for (int i = 0; i < properties.size(); i++) {
                // write the property
                CmsProperty property = properties.get(i);
                vfsDriver.writePropertyObject(dbc, dbc.currentProject(), resource, property);
            }

            if (updateStateValue > 0) {
                // update state
                updateState(dbc, resource, (updateStateValue == 2));
            }

            if (updateState) {
                // log it
                log(
                    dbc,
                    new CmsLogEntry(
                        dbc,
                        resource.getStructureId(),
                        CmsLogEntryType.RESOURCE_PROPERTIES,
                        new String[] {resource.getRootPath()}),
                    false);
            }
        } finally {
            // update the driver manager cache
            m_monitor.clearResourceCache();
            m_monitor.flushCache(CmsMemoryMonitor.CacheType.PROPERTY, CmsMemoryMonitor.CacheType.PROPERTY_LIST);

            // fire an event that the properties of a resource have been modified
            OpenCms.fireCmsEvent(
                new CmsEvent(
                    I_CmsEventListener.EVENT_RESOURCE_AND_PROPERTIES_MODIFIED,
                    Collections.<String, Object> singletonMap(I_CmsEventListener.KEY_RESOURCE, resource)));
        }
    }

    /**
     * Updates a publish job.<p>
     *
     * @param dbc the current database context
     * @param publishJob the publish job to update
     *
     * @throws CmsException if something goes wrong
     */
    public void writePublishJob(CmsDbContext dbc, CmsPublishJobInfoBean publishJob) throws CmsException {

        getProjectDriver(dbc).writePublishJob(dbc, publishJob);
    }

    /**
     * Writes the publish report for a publish job.<p>
     *
     * @param dbc the current database context
     * @param publishJob the publish job
     * @throws CmsException if something goes wrong
     */
    public void writePublishReport(CmsDbContext dbc, CmsPublishJobInfoBean publishJob) throws CmsException {

        CmsPublishReport report = (CmsPublishReport)publishJob.removePublishReport();

        if (report != null) {
            getProjectDriver(dbc).writePublishReport(dbc, publishJob.getPublishHistoryId(), report.getContents());
        }
    }

    /**
     * Writes a resource to the OpenCms VFS.<p>
     *
     * @param dbc the current database context
     * @param resource the resource to write
     *
     * @throws CmsException if something goes wrong
     */
    public void writeResource(CmsDbContext dbc, CmsResource resource) throws CmsException {

        // access was granted - write the resource
        resource.setUserLastModified(dbc.currentUser().getId());
        CmsUUID projectId = ((dbc.getProjectId() == null) || dbc.getProjectId().isNullUUID())
        ? dbc.currentProject().getUuid()
        : dbc.getProjectId();

        getVfsDriver(dbc).writeResource(dbc, projectId, resource, UPDATE_RESOURCE_STATE);

        // make sure the written resource has the state correctly set
        if (resource.getState().isUnchanged()) {
            resource.setState(CmsResource.STATE_CHANGED);
        }

        // delete in content relations if the new type is not parseable
        if (!(OpenCms.getResourceManager().getResourceType(resource.getTypeId()) instanceof I_CmsLinkParseable)) {
            deleteRelationsWithSiblings(dbc, resource);
        }

        // update the cache
        m_monitor.clearResourceCache();
        Map<String, Object> data = new HashMap<String, Object>(2);
        data.put(I_CmsEventListener.KEY_RESOURCE, resource);
        data.put(I_CmsEventListener.KEY_CHANGE, new Integer(CHANGED_RESOURCE));
        OpenCms.fireCmsEvent(new CmsEvent(I_CmsEventListener.EVENT_RESOURCE_MODIFIED, data));
    }

    /**
     * Inserts an entry in the published resource table.<p>
     *
     * This is done during static export.<p>
     *
     * @param dbc the current database context
     * @param resourceName The name of the resource to be added to the static export
     * @param linkType the type of resource exported (0= non-parameter, 1=parameter)
     * @param linkParameter the parameters added to the resource
     * @param timestamp a time stamp for writing the data into the db
     *
     * @throws CmsException if something goes wrong
     */
    public void writeStaticExportPublishedResource(
        CmsDbContext dbc,
        String resourceName,
        int linkType,
        String linkParameter,
        long timestamp)
    throws CmsException {

        getProjectDriver(dbc).writeStaticExportPublishedResource(dbc, resourceName, linkType, linkParameter, timestamp);
    }

    /**
     * Adds a new url name mapping for a structure id.<p>
     *
     * Instead of taking the name directly, this method takes an iterator of strings
     * which generates candidate URL names on-the-fly. The first generated name which is
     * not already mapped to another structure id will be chosen for the new URL name mapping.
     *
     * @param dbc the current database context
     * @param nameSeq the sequence of URL name candidates
     * @param structureId the structure id to which the url name should be mapped
     * @param locale the locale for which the mapping should be written
     * @param replaceOnPublish name mappings for which this is set will replace all other mappings for the same resource on publishing
     *
     * @return the actual name which was mapped to the structure id
     *
     * @throws CmsDataAccessException if something goes wrong
     */
    public String writeUrlNameMapping(
        CmsDbContext dbc,
        Iterator<String> nameSeq,
        CmsUUID structureId,
        String locale,
        boolean replaceOnPublish)
    throws CmsDataAccessException {

        String bestName = findBestNameForUrlNameMapping(dbc, nameSeq, structureId, locale);
        addOrReplaceUrlNameMapping(dbc, bestName, structureId, locale, replaceOnPublish);
        return bestName;
    }

    /**
     * Updates the user information. <p>
     *
     * The user id has to be a valid OpenCms user id.<br>
     *
     * The user with the given id will be completely overridden
     * by the given data.<p>
     *
     * @param dbc the current database context
     * @param user the user to be updated
     *
     * @throws CmsException if operation was not successful
     */
    public void writeUser(CmsDbContext dbc, CmsUser user) throws CmsException {

        CmsUser oldUser = readUser(dbc, user.getId());
        m_monitor.clearUserCache(oldUser);
        getUserDriver(dbc).writeUser(dbc, user);
        m_monitor.flushCache(CmsMemoryMonitor.CacheType.USERGROUPS, CmsMemoryMonitor.CacheType.USER_LIST);

        if (!dbc.getProjectId().isNullUUID()) {
            // user modified event is not needed
            return;
        }
        // fire user modified event
        Map<String, Object> eventData = new HashMap<String, Object>();
        eventData.put(I_CmsEventListener.KEY_USER_ID, user.getId().toString());
        eventData.put(I_CmsEventListener.KEY_USER_NAME, oldUser.getName());
        eventData.put(I_CmsEventListener.KEY_USER_ACTION, I_CmsEventListener.VALUE_USER_MODIFIED_ACTION_WRITE_USER);
        eventData.put(I_CmsEventListener.KEY_USER_CHANGES, Integer.valueOf(user.getChanges(oldUser)));
        OpenCms.fireCmsEvent(new CmsEvent(I_CmsEventListener.EVENT_USER_MODIFIED, eventData));
    }

    /**
     * Adds or replaces a new url name mapping in the offline project.<p>
     *
     * @param dbc the current database context
     * @param name the URL name of the mapping
     * @param structureId the structure id of the mapping
     * @param locale the locale of the mapping
     * @param replaceOnPublish if the mapping shoudl replace previous URL name mappings when published
     *
     * @throws CmsDataAccessException if something goes wrong
     */
    protected void addOrReplaceUrlNameMapping(
        CmsDbContext dbc,
        String name,
        CmsUUID structureId,
        String locale,
        boolean replaceOnPublish)
    throws CmsDataAccessException {

        getVfsDriver(dbc).deleteUrlNameMappingEntries(
            dbc,
            false,
            CmsUrlNameMappingFilter.ALL.filterStructureId(structureId).filterLocale(locale).filterStates(
                CmsUrlNameMappingEntry.MAPPING_STATUS_NEW,
                CmsUrlNameMappingEntry.MAPPING_STATUS_REPLACE_ON_PUBLISH));
        CmsUrlNameMappingEntry newEntry = new CmsUrlNameMappingEntry(
            name,
            structureId,
            replaceOnPublish
            ? CmsUrlNameMappingEntry.MAPPING_STATUS_REPLACE_ON_PUBLISH
            : CmsUrlNameMappingEntry.MAPPING_STATUS_NEW,
            System.currentTimeMillis(),
            locale);
        getVfsDriver(dbc).addUrlNameMappingEntry(dbc, false, newEntry);
    }

    /**
     * Converts a resource to a folder (if possible).<p>
     *
     * @param resource the resource to convert
     * @return the converted resource
     *
     * @throws CmsVfsResourceNotFoundException if the resource is not a folder
     */
    protected CmsFolder convertResourceToFolder(CmsResource resource) throws CmsVfsResourceNotFoundException {

        if (resource.isFolder()) {
            return new CmsFolder(resource);
        }

        throw new CmsVfsResourceNotFoundException(
            Messages.get().container(Messages.ERR_ACCESS_FILE_AS_FOLDER_1, resource.getRootPath()));
    }

    /**
     * Helper method for creating a driver from configuration data.<p>
     *
     * @param dbc the db context
     * @param configManager the configuration manager
     * @param config the configuration
     * @param driverChainKey the configuration key under which the driver chain is stored
     * @param suffix the suffix to append to a driver chain entry to get the key for the driver class
     *
     * @return the newly created driver
     */
    protected Object createDriver(
        CmsDbContext dbc,
        CmsConfigurationManager configManager,
        CmsParameterConfiguration config,
        String driverChainKey,
        String suffix) {

        // read the vfs driver class properties and initialize a new instance
        List<String> drivers = config.getList(driverChainKey);
        String driverKey = drivers.get(0) + suffix;
        String driverName = config.get(driverKey);
        drivers = (drivers.size() > 1) ? drivers.subList(1, drivers.size()) : null;
        if (driverName == null) {
            CmsLog.INIT.error(Messages.get().getBundle().key(Messages.INIT_DRIVER_FAILED_1, driverKey));
        }
        Object result = newDriverInstance(dbc, configManager, driverName, drivers);
        if ("true".equalsIgnoreCase(System.getProperty("opencms.profile.drivers"))) {
            result = wrapDriverInProfilingProxy(result);
        }
        return result;
    }

    /**
     * Deletes all relations for the given resource and all its siblings.<p>
     *
     * @param dbc the current database context
     * @param resource the resource to delete the resource for
     *
     * @throws CmsException if something goes wrong
     */
    protected void deleteRelationsWithSiblings(CmsDbContext dbc, CmsResource resource) throws CmsException {

        // get all siblings
        List<CmsResource> siblings;
        if (resource.getSiblingCount() > 1) {
            siblings = readSiblings(dbc, resource, CmsResourceFilter.ALL);
        } else {
            siblings = new ArrayList<CmsResource>();
            siblings.add(resource);
        }
        // clean the relations in content for all siblings
        I_CmsVfsDriver vfsDriver = getVfsDriver(dbc);
        Iterator<CmsResource> it = siblings.iterator();
        while (it.hasNext()) {
            CmsResource sibling = it.next();
            // clean the relation information for this sibling
            vfsDriver.deleteRelations(
                dbc,
                dbc.currentProject().getUuid(),
                sibling,
                CmsRelationFilter.TARGETS.filterDefinedInContent());
        }
    }

    /**
     * Tries to add sub-resources of moved folders to the publish list and throws an exception if the publish list still does
     * not contain some  sub-resources of the moved folders.<p>
     *
     * @param cms the current CMS context
     * @param dbc the current database context
     * @param pubList the publish list
     * @throws CmsException if something goes wrong
     */
    protected void ensureSubResourcesOfMovedFoldersPublished(CmsObject cms, CmsDbContext dbc, CmsPublishList pubList)
    throws CmsException {

        List<CmsResource> topMovedFolders = pubList.getTopMovedFolders(cms);
        Iterator<CmsResource> folderIt = topMovedFolders.iterator();
        while (folderIt.hasNext()) {
            CmsResource folder = folderIt.next();
            addSubResources(dbc, pubList, folder, resource -> !resource.getState().isNew());
        }
        List<CmsResource> missingSubResources = pubList.getMissingSubResources(cms, topMovedFolders);
        if (missingSubResources.isEmpty()) {
            return;
        }

        StringBuffer pathBuffer = new StringBuffer();

        for (CmsResource missing : missingSubResources) {
            pathBuffer.append(missing.getRootPath());
            pathBuffer.append(" ");
        }
        throw new CmsVfsException(
            Messages.get().container(Messages.RPT_CHILDREN_OF_MOVED_FOLDER_NOT_PUBLISHED_1, pathBuffer.toString()));

    }

    /**
     * Tries to find the best name for an URL name mapping for the given structure id.<p>
     *
     * @param dbc the database context
     * @param nameSeq the sequence of name candidates
     * @param structureId the structure id to which an URL name should be mapped
     * @param locale the locale for which the URL name should be mapped
     *
     * @return the selected URL name candidate
     *
     * @throws CmsDataAccessException if something goes wrong
     */
    protected String findBestNameForUrlNameMapping(
        CmsDbContext dbc,
        Iterator<String> nameSeq,
        CmsUUID structureId,
        String locale)
    throws CmsDataAccessException {

        String newName;
        boolean alreadyInUse;
        do {
            newName = nameSeq.next();
            alreadyInUse = false;
            CmsUrlNameMappingFilter filter = CmsUrlNameMappingFilter.ALL.filterName(newName);
            List<CmsUrlNameMappingEntry> entriesWithSameName = getVfsDriver(dbc).readUrlNameMappingEntries(
                dbc,
                false,
                filter);
            for (CmsUrlNameMappingEntry entry : entriesWithSameName) {
                boolean sameId = entry.getStructureId().equals(structureId);
                if (!sameId) {
                    // name already used for other resource, or for different locale of the same resource
                    alreadyInUse = true;
                    break;
                }
            }
        } while (alreadyInUse);
        return newName;
    }

    /**
     * Helper method for finding the 'best' URL name to use for a new URL name mapping.<p>
     *
     * Since the name given as a parameter may be already used, this method will try to append numeric suffixes
     * to the name to find a mapping name which is not used.<p>
     *
     * @param dbc the current database context
     * @param name the name of the mapping
     * @param structureId the structure id to which the name is mapped
     *
     * @return the best name which was found for the new mapping
     *
     * @throws CmsDataAccessException if something goes wrong
     */
    protected String findBestNameForUrlNameMapping(CmsDbContext dbc, String name, CmsUUID structureId)
    throws CmsDataAccessException {

        List<CmsUrlNameMappingEntry> entriesStartingWithName = getVfsDriver(dbc).readUrlNameMappingEntries(
            dbc,
            false,
            CmsUrlNameMappingFilter.ALL.filterNamePattern(name + "%").filterRejectStructureId(structureId));
        Set<String> usedNames = new HashSet<String>();
        for (CmsUrlNameMappingEntry entry : entriesStartingWithName) {
            usedNames.add(entry.getName());
        }
        int counter = 0;
        String numberedName;
        do {
            numberedName = getNumberedName(name, counter);
            counter += 1;
        } while (usedNames.contains(numberedName));
        return numberedName;
    }

    /**
     * Returns the lock manager instance.<p>
     *
     * @return the lock manager instance
     */
    protected CmsLockManager getLockManager() {

        return m_lockManager;
    }

    /**
     * Adds a numeric suffix to the end of a string, unless the number passed as a parameter is 0.<p>
     *
     * @param name the base name
     * @param number the number from which to form the suffix
     *
     * @return the concatenation of the base name and possibly the numeric suffix
     */
    protected String getNumberedName(String name, int number) {

        if (number == 0) {
            return name;
        }
        PrintfFormat fmt = new PrintfFormat("%0.6d");
        return name + "_" + fmt.sprintf(number);
    }

    /**
     * Resets the resources in a project to their online state.<p>
     *
     * @param dbc the database context
     * @param projectId the project id
     * @param modifiedFiles the modified files
     * @param modifiedFolders the modified folders
     * @throws CmsException if something goes wrong
     * @throws CmsSecurityException if we don't have the permissions
     * @throws CmsDataAccessException if something goes wrong with the database
     */
    protected void resetResourcesInProject(
        CmsDbContext dbc,
        CmsUUID projectId,
        List<CmsResource> modifiedFiles,
        List<CmsResource> modifiedFolders)
    throws CmsException, CmsSecurityException, CmsDataAccessException {

        // all resources inside the project have to be be reset to their online state.
        // 1. step: delete all new files
        for (int i = 0; i < modifiedFiles.size(); i++) {
            CmsResource currentFile = modifiedFiles.get(i);
            if (currentFile.getState().isNew()) {
                CmsLock lock = getLock(dbc, currentFile);
                if (lock.isNullLock()) {
                    // lock the resource
                    lockResource(dbc, currentFile, CmsLockType.EXCLUSIVE);
                } else if (!lock.isOwnedBy(dbc.currentUser()) || !lock.isInProject(dbc.currentProject())) {
                    changeLock(dbc, currentFile, CmsLockType.EXCLUSIVE);
                }
                // delete the properties
                getVfsDriver(dbc).deletePropertyObjects(
                    dbc,
                    projectId,
                    currentFile,
                    CmsProperty.DELETE_OPTION_DELETE_STRUCTURE_AND_RESOURCE_VALUES);
                // delete the file
                getVfsDriver(dbc).removeFile(dbc, dbc.currentProject().getUuid(), currentFile);
                // remove the access control entries
                getUserDriver(dbc).removeAccessControlEntries(dbc, dbc.currentProject(), currentFile.getResourceId());
                // fire the corresponding event
                OpenCms.fireCmsEvent(
                    new CmsEvent(
                        I_CmsEventListener.EVENT_RESOURCE_AND_PROPERTIES_MODIFIED,
                        Collections.<String, Object> singletonMap(I_CmsEventListener.KEY_RESOURCE, currentFile)));
            }
        }

        // 2. step: delete all new folders
        for (int i = 0; i < modifiedFolders.size(); i++) {
            CmsResource currentFolder = modifiedFolders.get(i);
            if (currentFolder.getState().isNew()) {
                // delete the properties
                getVfsDriver(dbc).deletePropertyObjects(
                    dbc,
                    projectId,
                    currentFolder,
                    CmsProperty.DELETE_OPTION_DELETE_STRUCTURE_AND_RESOURCE_VALUES);
                // delete the folder
                getVfsDriver(dbc).removeFolder(dbc, dbc.currentProject(), currentFolder);
                // remove the access control entries
                getUserDriver(dbc).removeAccessControlEntries(dbc, dbc.currentProject(), currentFolder.getResourceId());
                // fire the corresponding event
                OpenCms.fireCmsEvent(
                    new CmsEvent(
                        I_CmsEventListener.EVENT_RESOURCE_AND_PROPERTIES_MODIFIED,
                        Collections.<String, Object> singletonMap(I_CmsEventListener.KEY_RESOURCE, currentFolder)));
            }
        }

        // 3. step: undo changes on all changed or deleted folders
        for (int i = 0; i < modifiedFolders.size(); i++) {
            CmsResource currentFolder = modifiedFolders.get(i);
            if ((currentFolder.getState().isChanged()) || (currentFolder.getState().isDeleted())) {
                CmsLock lock = getLock(dbc, currentFolder);
                if (lock.isNullLock()) {
                    // lock the resource
                    lockResource(dbc, currentFolder, CmsLockType.EXCLUSIVE);
                } else if (!lock.isOwnedBy(dbc.currentUser()) || !lock.isInProject(dbc.currentProject())) {
                    changeLock(dbc, currentFolder, CmsLockType.EXCLUSIVE);
                }
                // undo all changes in the folder
                undoChanges(dbc, currentFolder, CmsResource.UNDO_CONTENT);
                // fire the corresponding event
                OpenCms.fireCmsEvent(
                    new CmsEvent(
                        I_CmsEventListener.EVENT_RESOURCE_AND_PROPERTIES_MODIFIED,
                        Collections.<String, Object> singletonMap(I_CmsEventListener.KEY_RESOURCE, currentFolder)));
            }
        }

        // 4. step: undo changes on all changed or deleted files
        for (int i = 0; i < modifiedFiles.size(); i++) {
            CmsResource currentFile = modifiedFiles.get(i);
            if (currentFile.getState().isChanged() || currentFile.getState().isDeleted()) {
                CmsLock lock = getLock(dbc, currentFile);
                if (lock.isNullLock()) {
                    // lock the resource
                    lockResource(dbc, currentFile, CmsLockType.EXCLUSIVE);
                } else if (!lock.isOwnedInProjectBy(dbc.currentUser(), dbc.currentProject())) {
                    if (lock.isLockableBy(dbc.currentUser())) {
                        changeLock(dbc, currentFile, CmsLockType.EXCLUSIVE);
                    }
                }
                // undo all changes in the file
                undoChanges(dbc, currentFile, CmsResource.UNDO_CONTENT);
                // fire the corresponding event
                OpenCms.fireCmsEvent(
                    new CmsEvent(
                        I_CmsEventListener.EVENT_RESOURCE_AND_PROPERTIES_MODIFIED,
                        Collections.<String, Object> singletonMap(I_CmsEventListener.KEY_RESOURCE, currentFile)));
            }
        }
    }

    /**
     * Counts the total number of users which fit the given criteria.<p>
     *
     * @param dbc the database context
     * @param searchParams the user search criteria
     *
     * @return the total number of users matching the criteria
     *
     * @throws CmsDataAccessException if something goes wrong
     */
    long countUsers(CmsDbContext dbc, CmsUserSearchParameters searchParams) throws CmsDataAccessException {

        return getUserDriver(dbc).countUsers(dbc, searchParams);
    }

    /**
     * Adds a pool to the static pool map.<p>
     *
     * @param pool the pool to add
     */
    private void addPool(CmsDbPoolV11 pool) {

        m_pools.put(pool.getPoolUrl(), pool);
    }

    /**
     * Adds all sub-resources of the given resource to the publish list.<p>
     *
     * @param dbc the database context
     * @param publishList the publish list
     * @param directPublishResource the resource to get the sub-resources for
     * @param additionalFilter an additional test for resources to pass before they are added to the publish list
     *
     * @throws CmsDataAccessException if something goes wrong accessing the database
     */
    private void addSubResources(
        CmsDbContext dbc,
        CmsPublishList publishList,
        CmsResource directPublishResource,
        Predicate<CmsResource> additionalFilter)
    throws CmsDataAccessException {

        int flags = CmsDriverManager.READMODE_INCLUDE_TREE | CmsDriverManager.READMODE_EXCLUDE_STATE;
        if (!directPublishResource.getState().isDeleted()) {
            // fix for org.opencms.file.TestPublishIssues#testPublishFolderWithDeletedFileFromOtherProject
            flags = flags | CmsDriverManager.READMODE_INCLUDE_PROJECT;
        }

        // add all sub resources of the folder
        List<CmsResource> folderList = getVfsDriver(dbc).readResourceTree(
            dbc,
            dbc.currentProject().getUuid(),
            directPublishResource.getRootPath(),
            CmsDriverManager.READ_IGNORE_TYPE,
            CmsResource.STATE_UNCHANGED,
            CmsDriverManager.READ_IGNORE_TIME,
            CmsDriverManager.READ_IGNORE_TIME,
            CmsDriverManager.READ_IGNORE_TIME,
            CmsDriverManager.READ_IGNORE_TIME,
            CmsDriverManager.READ_IGNORE_TIME,
            CmsDriverManager.READ_IGNORE_TIME,
            flags | CmsDriverManager.READMODE_ONLY_FOLDERS);

        publishList.addAll(
            filterResources(dbc, publishList, folderList).stream().filter(additionalFilter).collect(
                Collectors.toList()),
            true);

        List<CmsResource> fileList = getVfsDriver(dbc).readResourceTree(
            dbc,
            dbc.currentProject().getUuid(),
            directPublishResource.getRootPath(),
            CmsDriverManager.READ_IGNORE_TYPE,
            CmsResource.STATE_UNCHANGED,
            CmsDriverManager.READ_IGNORE_TIME,
            CmsDriverManager.READ_IGNORE_TIME,
            CmsDriverManager.READ_IGNORE_TIME,
            CmsDriverManager.READ_IGNORE_TIME,
            CmsDriverManager.READ_IGNORE_TIME,
            CmsDriverManager.READ_IGNORE_TIME,
            flags | CmsDriverManager.READMODE_ONLY_FILES);

        publishList.addAll(
            filterResources(dbc, publishList, fileList).stream().filter(additionalFilter).collect(Collectors.toList()),
            true);
    }

    /**
     * Helper method to check whether we should bother with reading the group for a given role in a given OU.<p>
     *
     * This is important because webuser OUs don't have most role groups, and their absence is not cached, so we want to avoid reading them.
     *
     * @param ou the OU
     * @param role the role
     * @return true if we should read the role in the OU
     */
    private boolean canReadRoleInOu(CmsOrganizationalUnit ou, CmsRole role) {

        if (ou.hasFlagWebuser() && !role.getRoleName().equals(CmsRole.ACCOUNT_MANAGER.getRoleName())) {
            return false;
        }
        return true;
    }

    /**
     * Checks the parent of a resource during publishing.<p>
     *
     * @param dbc the current database context
     * @param deletedFolders a list of deleted folders
     * @param res a resource to check the parent for
     *
     * @return <code>true</code> if the parent resource will be deleted during publishing
     */
    private boolean checkDeletedParentFolder(CmsDbContext dbc, List<CmsResource> deletedFolders, CmsResource res) {

        String parentPath = CmsResource.getParentFolder(res.getRootPath());

        if (parentPath == null) {
            // resource has no parent
            return false;
        }

        CmsResource parent;
        try {
            parent = readResource(dbc, parentPath, CmsResourceFilter.ALL);
        } catch (Exception e) {
            // failure: if we cannot read the parent, we should not publish the resource
            return false;
        }

        if (!parent.getState().isDeleted()) {
            // parent is not deleted
            return false;
        }

        for (int j = 0; j < deletedFolders.size(); j++) {
            if ((deletedFolders.get(j)).getStructureId().equals(parent.getStructureId())) {
                // parent is deleted, and it will get published
                return true;
            }
        }

        // parent is new, but it will not get published
        return false;
    }

    /**
     * Checks that no one of the resources to be published has a 'new' parent (that has not been published yet).<p>
     *
     * @param dbc the db context
     * @param publishList the publish list to check
     *
     * @throws CmsVfsException if there is a resource to be published with a 'new' parent
     */
    private void checkParentFolders(CmsDbContext dbc, CmsPublishList publishList) throws CmsVfsException {

        boolean directPublish = publishList.isDirectPublish();
        // if we direct publish a file, check if all parent folders are already published
        if (directPublish) {
            // first get the names of all parent folders
            Iterator<CmsResource> it = publishList.getDirectPublishResources().iterator();
            List<String> parentFolderNames = new ArrayList<String>();
            while (it.hasNext()) {
                CmsResource res = it.next();
                String parentFolderName = CmsResource.getParentFolder(res.getRootPath());
                if (parentFolderName != null) {
                    parentFolderNames.add(parentFolderName);
                }
            }
            // remove duplicate parent folder names
            parentFolderNames = CmsFileUtil.removeRedundancies(parentFolderNames);
            String parentFolderName = null;
            try {
                I_CmsVfsDriver vfsDriver = getVfsDriver(dbc);
                // now check all folders if they exist in the online project
                Iterator<String> parentIt = parentFolderNames.iterator();
                while (parentIt.hasNext()) {
                    parentFolderName = parentIt.next();
                    vfsDriver.readFolder(dbc, CmsProject.ONLINE_PROJECT_ID, parentFolderName);
                }
            } catch (CmsException e) {
                throw new CmsVfsException(
                    Messages.get().container(Messages.RPT_PARENT_FOLDER_NOT_PUBLISHED_1, parentFolderName));
            }
        }
    }

    /**
     * Checks the parent of a resource during publishing.<p>
     *
     * @param dbc the current database context
     * @param folderList a list of folders
     * @param res a resource to check the parent for
     *
     * @return true if the resource should be published
     */
    private boolean checkParentResource(CmsDbContext dbc, List<CmsResource> folderList, CmsResource res) {

        String parentPath = CmsResource.getParentFolder(res.getRootPath());

        if (parentPath == null) {
            // resource has no parent
            return true;
        }

        CmsResource parent;
        try {
            parent = readResource(dbc, parentPath, CmsResourceFilter.ALL);
        } catch (Exception e) {
            // failure: if we cannot read the parent, we should not publish the resource
            return false;
        }

        if (!parent.getState().isNew()) {
            // parent is already published
            return true;
        }

        for (int j = 0; j < folderList.size(); j++) {
            if (folderList.get(j).getStructureId().equals(parent.getStructureId())) {
                // parent is new, but it will get published
                return true;
            }
        }

        // parent is new, but it will not get published
        return false;
    }

    /**
     * Copies all relations from the source resource to the target resource.<p>
     *
     * @param dbc the database context
     * @param source the source
     * @param target the target
     *
     * @throws CmsException if something goes wrong
     */
    private void copyRelations(CmsDbContext dbc, CmsResource source, CmsResource target) throws CmsException {

        // copy relations all relations
        CmsObject cms = new CmsObject(getSecurityManager(), dbc.getRequestContext());
        Iterator<CmsRelation> itRelations = getRelationsForResource(
            dbc,
            source,
            CmsRelationFilter.TARGETS.filterNotDefinedInContent()).iterator();
        while (itRelations.hasNext()) {
            CmsRelation relation = itRelations.next();
            if (relation.getType().getCopyBehavior() == CopyBehavior.copy) {
                try {
                    CmsResource relTarget = relation.getTarget(cms, CmsResourceFilter.ALL);
                    addRelationToResource(dbc, target, relTarget, relation.getType(), true);
                } catch (CmsVfsResourceNotFoundException e) {
                    // ignore this broken relation
                    if (LOG.isWarnEnabled()) {
                        LOG.warn(e.getLocalizedMessage(), e);
                    }
                }
            }
        }
        // repair categories
        repairCategories(dbc, getProjectIdForContext(dbc), target);
    }

    /**
     * Filters the given list of resources, removes all resources where the current user
     * does not have READ permissions, plus the filter is applied.<p>
     *
     * @param dbc the current database context
     * @param resourceList a list of CmsResources
     * @param filter the resource filter to use
     *
     * @return the filtered list of resources
     *
     * @throws CmsException in case errors testing the permissions
     */
    private List<CmsResource> filterPermissions(
        CmsDbContext dbc,
        List<CmsResource> resourceList,
        CmsResourceFilter filter)
    throws CmsException {

        if (filter.requireTimerange()) {
            // never check time range here - this must be done later in #updateContextDates(...)
            filter = filter.addExcludeTimerange();
        }
        ArrayList<CmsResource> result = new ArrayList<CmsResource>(resourceList.size());
        for (int i = 0; i < resourceList.size(); i++) {
            // check the permission of all resources
            CmsResource currentResource = resourceList.get(i);
            if (m_securityManager.hasPermissions(
                dbc,
                currentResource,
                CmsPermissionSet.ACCESS_READ,
                true,
                filter).isAllowed()) {
                // only return resources where permission was granted
                result.add(currentResource);
            }
        }
        // return the result
        return result;
    }

    /**
     * Returns a filtered list of resources for publishing.<p>
     * Contains all resources, which are not locked
     * and which have a parent folder that is already published or will be published, too.<p>
     *
     * @param dbc the current database context
     * @param publishList the filling publish list
     * @param resourceList the list of resources to filter
     *
     * @return a filtered list of resources
     */
    private List<CmsResource> filterResources(
        CmsDbContext dbc,
        CmsPublishList publishList,
        List<CmsResource> resourceList) {

        List<CmsResource> result = new ArrayList<CmsResource>();

        // local folder list for adding new publishing subfolders
        // this solves the {@link org.opencms.file.TestPublishIssues#testPublishScenarioD} problem.
        List<CmsResource> newFolderList = new ArrayList<CmsResource>(
            publishList == null ? resourceList : publishList.getFolderList());

        for (int i = 0; i < resourceList.size(); i++) {
            CmsResource res = resourceList.get(i);
            try {
                CmsLock lock = getLock(dbc, res);
                if (lock.isPublish()) {
                    // if already enqueued
                    continue;
                }
                if (!lock.isLockableBy(dbc.currentUser())) {
                    // checks if there is a shared lock and if the resource is deleted
                    // this solves the {@link org.opencms.file.TestPublishIssues#testPublishScenarioE} problem.
                    if (lock.isShared() && (publishList != null)) {
                        if (!res.getState().isDeleted()
                            || !checkDeletedParentFolder(dbc, publishList.getDeletedFolderList(), res)) {
                            continue;
                        }
                    } else {
                        // don't add locked resources
                        continue;
                    }
                }
                if (!"/".equals(res.getRootPath()) && !checkParentResource(dbc, newFolderList, res)) {
                    continue;
                }
                // check permissions
                try {
                    m_securityManager.checkPermissions(
                        dbc,
                        res,
                        CmsPermissionSet.ACCESS_DIRECT_PUBLISH,
                        false,
                        CmsResourceFilter.ALL);
                } catch (CmsException e) {
                    // skip if not enough permissions
                    continue;
                }
                if (res.isFolder()) {
                    newFolderList.add(res);
                }
                result.add(res);
            } catch (Exception e) {
                // should never happen
                LOG.error(e.getLocalizedMessage(), e);
            }
        }
        return result;
    }

    /**
     * Returns a filtered list of sibling resources for publishing.<p>
     *
     * Contains all siblings of the given resources, which are not locked
     * and which have a parent folder that is already published or will be published, too.<p>
     *
     * @param dbc the current database context
     * @param publishList the unfinished publish list
     * @param resourceList the list of siblings to filter
     *
     * @return a filtered list of sibling resources for publishing
     */
    private List<CmsResource> filterSiblings(
        CmsDbContext dbc,
        CmsPublishList publishList,
        Collection<CmsResource> resourceList) {

        List<CmsResource> result = new ArrayList<CmsResource>();

        // removed internal extendible folder list, since iterated (sibling) resources are files in any case, never folders

        for (CmsResource res : resourceList) {
            try {
                CmsLock lock = getLock(dbc, res);
                if (lock.isPublish()) {
                    // if already enqueued
                    continue;
                }
                if (!lock.isLockableBy(dbc.currentUser())) {
                    // checks if there is a shared lock and if the resource is deleted
                    // this solves the {@link org.opencms.file.TestPublishIssues#testPublishScenarioE} problem.
                    if (lock.isShared() && (publishList != null)) {
                        if (!res.getState().isDeleted()
                            || !checkDeletedParentFolder(dbc, publishList.getDeletedFolderList(), res)) {
                            continue;
                        }
                    } else {
                        // don't add locked resources
                        continue;
                    }
                }
                if (!"/".equals(res.getRootPath()) && !checkParentResource(dbc, publishList.getFolderList(), res)) {
                    // don't add resources that have no parent in the online project
                    continue;
                }
                // check permissions
                try {
                    m_securityManager.checkPermissions(
                        dbc,
                        res,
                        CmsPermissionSet.ACCESS_DIRECT_PUBLISH,
                        false,
                        CmsResourceFilter.ALL);
                } catch (CmsException e) {
                    // skip if not enough permissions
                    continue;
                }
                result.add(res);
            } catch (Exception e) {
                // should never happen
                LOG.error(e.getLocalizedMessage(), e);
            }
        }
        return result;
    }

    /**
     * Returns the access control list of a given resource.<p>
     *
     * @param dbc the current database context
     * @param resource the resource
     * @param forFolder should be true if resource is a folder
     * @param depth the depth to include non-inherited access entries, also
     * @param inheritedOnly flag indicates to collect inherited permissions only
     *
     * @return the access control list of the resource
     *
     * @throws CmsException if something goes wrong
     */
    private CmsAccessControlList getAccessControlList(
        CmsDbContext dbc,
        CmsResource resource,
        boolean inheritedOnly,
        boolean forFolder,
        int depth)
    throws CmsException {

        String cacheKey = getCacheKey(
            new String[] {
                inheritedOnly ? "+" : "-",
                forFolder ? "+" : "-",
                Integer.toString(depth),
                resource.getStructureId().toString()},
            dbc);

        CmsAccessControlList acl = m_monitor.getCachedACL(cacheKey);

        // return the cached acl if already available
        if ((acl != null) && dbc.getProjectId().isNullUUID()) {
            return acl;
        }

        List<CmsAccessControlEntry> aces = getUserDriver(dbc).readAccessControlEntries(
            dbc,
            dbc.currentProject(),
            resource.getResourceId(),
            (depth > 1) || ((depth > 0) && forFolder));

        // sort the list of aces
        boolean overwriteAll = sortAceList(aces);

        // if no 'overwrite all' ace was found
        if (!overwriteAll) {
            // get the acl of the parent
            CmsResource parentResource = null;
            try {
                // try to recurse over the id
                parentResource = getVfsDriver(dbc).readParentFolder(
                    dbc,
                    dbc.currentProject().getUuid(),
                    resource.getStructureId());
            } catch (CmsVfsResourceNotFoundException e) {
                // should never happen, but try with the path
                String parentPath = CmsResource.getParentFolder(resource.getRootPath());
                if (parentPath != null) {
                    parentResource = getVfsDriver(dbc).readFolder(dbc, dbc.currentProject().getUuid(), parentPath);
                }
            }
            if (parentResource != null) {
                acl = (CmsAccessControlList)getAccessControlList(
                    dbc,
                    parentResource,
                    inheritedOnly,
                    forFolder,
                    depth + 1).clone();
            }
        }
        if (acl == null) {
            acl = new CmsAccessControlList();
        }

        if (!((depth == 0) && inheritedOnly)) {
            Iterator<CmsAccessControlEntry> itAces = aces.iterator();
            while (itAces.hasNext()) {
                CmsAccessControlEntry acEntry = itAces.next();
                if (depth > 0) {
                    acEntry.setFlags(CmsAccessControlEntry.ACCESS_FLAGS_INHERITED);
                }

                acl.add(acEntry);

                // if the overwrite flag is set, reset the allowed permissions to the permissions of this entry
                // denied permissions are kept or extended
                if ((acEntry.getFlags() & CmsAccessControlEntry.ACCESS_FLAGS_OVERWRITE) > 0) {
                    acl.setAllowedPermissions(acEntry);
                }
            }
        }
        if (dbc.getProjectId().isNullUUID()) {
            m_monitor.cacheACL(cacheKey, acl);
        }
        return acl;
    }

    /**
     * Return a cache key build from the provided information.<p>
     *
     * @param prefix a prefix for the key
     * @param flag a boolean flag for the key (only used if prefix is not null)
     * @param projectId the project for which to generate the key
     * @param resource the resource for which to generate the key
     *
     * @return String a cache key build from the provided information
     */
    private String getCacheKey(String prefix, boolean flag, CmsUUID projectId, String resource) {

        StringBuffer b = new StringBuffer(64);
        if (prefix != null) {
            b.append(prefix);
            b.append(flag ? '+' : '-');
        }
        b.append(CmsProject.isOnlineProject(projectId) ? '+' : '-');
        return b.append(resource).toString();
    }

    /**
     * Return a cache key build from the provided information.<p>
     *
     * @param keys an array of keys to generate the cache key from
     * @param dbc the database context for which to generate the key
     *
     * @return String a cache key build from the provided information
     */
    private String getCacheKey(String[] keys, CmsDbContext dbc) {

        if (!dbc.getProjectId().isNullUUID()) {
            return "";
        }
        StringBuffer b = new StringBuffer(64);
        int len = keys.length;
        if (len > 0) {
            for (int i = 0; i < len; i++) {
                b.append(keys[i]);
                b.append('_');
            }
        }
        if (dbc.currentProject().isOnlineProject()) {
            b.append("+");
        } else {
            b.append("-");
        }
        return b.toString();
    }

    /**
     * Gets the correct driver interface to use for proxying a specific driver instance.<p>
     *
     * @param obj the driver instance
     * @return the interface to use for proxying
     */
    private Class<?> getDriverInterfaceForProxy(Object obj) {

        for (Class<?> interfaceClass : new Class[] {
            I_CmsUserDriver.class,
            I_CmsVfsDriver.class,
            I_CmsProjectDriver.class,
            I_CmsHistoryDriver.class,
            I_CmsSubscriptionDriver.class}) {
            if (interfaceClass.isAssignableFrom(obj.getClass())) {
                return interfaceClass;
            }
        }
        return null;
    }

    /**
     * Returns the correct project id.<p>
     *
     * @param dbc the database context
     *
     * @return the correct project id
     */
    private CmsUUID getProjectIdForContext(CmsDbContext dbc) {

        CmsUUID projectId = dbc.getProjectId();
        if (projectId.isNullUUID()) {
            projectId = dbc.currentProject().getUuid();
        }
        return projectId;
    }

    /**
     * Returns if and what state needs to be updated.<p>
     *
     * @param dbc the db context
     * @param resource the resource
     * @param properties the properties to check
     *
     * @return 0: none, 1: structure, 2: resource
     *
     * @throws CmsDataAccessException if something goes wrong
     */
    private int getUpdateState(CmsDbContext dbc, CmsResource resource, List<CmsProperty> properties)
    throws CmsDataAccessException {

        int updateState = 0;
        I_CmsVfsDriver vfsDriver = getVfsDriver(dbc);
        Iterator<CmsProperty> it = properties.iterator();
        while (it.hasNext() && (updateState < 2)) {
            CmsProperty property = it.next();

            // read existing property
            CmsProperty existingProperty = vfsDriver.readPropertyObject(
                dbc,
                property.getName(),
                dbc.currentProject(),
                resource);

            // check the shared property
            if (property.getResourceValue() != null) {
                if (property.isDeleteResourceValue()) {
                    if (existingProperty.getResourceValue() != null) {
                        updateState = 2; // deleted
                    }
                } else {
                    if (existingProperty.getResourceValue() == null) {
                        updateState = 2; // created
                    } else {
                        if (!property.getResourceValue().equals(existingProperty.getResourceValue())) {
                            updateState = 2; // updated
                        }
                    }
                }
            }
            if (updateState == 0) {
                // check the individual property only if needed
                if (property.getStructureValue() != null) {
                    if (property.isDeleteStructureValue()) {
                        if (existingProperty.getStructureValue() != null) {
                            updateState = 1; // deleted
                        }
                    } else {
                        if (existingProperty.getStructureValue() == null) {
                            updateState = 1; // created
                        } else {
                            if (!property.getStructureValue().equals(existingProperty.getStructureValue())) {
                                updateState = 1; // updated
                            }
                        }
                    }
                }
            }
        }
        return updateState;
    }

    /**
     * Returns all groups that are virtualizing the given role in the given ou.<p>
     *
     * @param dbc the database context
     * @param role the role
     *
     * @return all groups that are virtualizing the given role (or a child of it)
     *
     * @throws CmsException if something goes wrong
     */
    private List<CmsGroup> getVirtualGroupsForRole(CmsDbContext dbc, CmsRole role) throws CmsException {

        Set<Integer> roleFlags = new HashSet<Integer>();
        // add role flag
        Integer flags = new Integer(role.getVirtualGroupFlags());
        roleFlags.add(flags);
        // collect all child role flags
        Iterator<CmsRole> itChildRoles = role.getChildren(true).iterator();
        while (itChildRoles.hasNext()) {
            CmsRole child = itChildRoles.next();
            flags = new Integer(child.getVirtualGroupFlags());
            roleFlags.add(flags);
        }
        // iterate all groups matching the flags
        List<CmsGroup> groups = new ArrayList<CmsGroup>();
        Iterator<CmsGroup> it = getGroups(dbc, readOrganizationalUnit(dbc, role.getOuFqn()), false, false).iterator();
        while (it.hasNext()) {
            CmsGroup group = it.next();
            if (group.isVirtual()) {
                CmsRole r = CmsRole.valueOf(group);
                if (roleFlags.contains(new Integer(r.getVirtualGroupFlags()))) {
                    groups.add(group);
                }
            }
        }
        return groups;
    }

    /**
     * Returns a list of users in a group.<p>
     *
     * @param dbc the current database context
     * @param ouFqn the organizational unit to get the users from
     * @param groupname the name of the group to list users from
     * @param includeOtherOuUsers include users of other organizational units
     * @param directUsersOnly if set only the direct assigned users will be returned,
     *                        if not also indirect users, ie. members of parent roles,
     *                        this parameter only works with roles
     * @param readRoles if to read roles or groups
     *
     * @return all <code>{@link CmsUser}</code> objects in the group
     *
     * @throws CmsException if operation was not successful
     */
    private List<CmsUser> internalUsersOfGroup(
        CmsDbContext dbc,
        String ouFqn,
        String groupname,
        boolean includeOtherOuUsers,
        boolean directUsersOnly,
        boolean readRoles)
    throws CmsException {

        CmsGroup group = readGroup(dbc, groupname); // check that the group really exists
        if ((group == null) || (!((!readRoles && !group.isRole()) || (readRoles && group.isRole())))) {
            throw new CmsDbEntryNotFoundException(Messages.get().container(Messages.ERR_UNKNOWN_GROUP_1, groupname));
        }

        String prefix = "_" + includeOtherOuUsers + "_" + directUsersOnly + "_" + ouFqn;
        String cacheKey = m_keyGenerator.getCacheKeyForGroupUsers(prefix, dbc, group);
        List<CmsUser> allUsers = m_monitor.getCachedUserList(cacheKey);
        if (allUsers == null) {
            Set<CmsUser> users = new HashSet<CmsUser>(
                getUserDriver(dbc).readUsersOfGroup(dbc, groupname, includeOtherOuUsers));
            if (readRoles && !directUsersOnly) {
                CmsRole role = CmsRole.valueOf(group);
                if (role.getParentRole() != null) {
                    try {
                        String parentGroup = role.getParentRole().getGroupName();
                        readGroup(dbc, parentGroup);
                        // iterate the parent roles
                        users.addAll(
                            internalUsersOfGroup(
                                dbc,
                                ouFqn,
                                parentGroup,
                                includeOtherOuUsers,
                                directUsersOnly,
                                readRoles));
                    } catch (CmsDbEntryNotFoundException e) {
                        // ignore, this may happen while deleting an orgunit
                        if (LOG.isDebugEnabled()) {
                            LOG.debug(e.getLocalizedMessage(), e);
                        }
                    }
                }
                String parentOu = CmsOrganizationalUnit.getParentFqn(group.getOuFqn());
                if (parentOu != null) {
                    // iterate the parent ou's
                    users.addAll(
                        internalUsersOfGroup(
                            dbc,
                            ouFqn,
                            parentOu + group.getSimpleName(),
                            includeOtherOuUsers,
                            directUsersOnly,
                            readRoles));
                }
            } else if (!readRoles && !directUsersOnly) {
                List<CmsGroup> groups = getChildren(dbc, group, false);
                for (CmsGroup parentGroup : groups) {
                    try {
                        // iterate the parent groups
                        users.addAll(
                            internalUsersOfGroup(
                                dbc,
                                ouFqn,
                                parentGroup.getName(),
                                includeOtherOuUsers,
                                directUsersOnly,
                                readRoles));
                    } catch (CmsDbEntryNotFoundException e) {
                        // ignore, this may happen while deleting an orgunit
                        if (LOG.isDebugEnabled()) {
                            LOG.debug(e.getLocalizedMessage(), e);
                        }
                    }
                }
            }
            // filter users from other ous
            if (!includeOtherOuUsers) {
                Iterator<CmsUser> itUsers = users.iterator();
                while (itUsers.hasNext()) {
                    CmsUser user = itUsers.next();
                    if (!user.getOuFqn().equals(ouFqn)) {
                        itUsers.remove();
                    }
                }
            }

            // make user list unmodifiable for caching
            allUsers = Collections.unmodifiableList(new ArrayList<CmsUser>(users));
            if (dbc.getProjectId().isNullUUID()) {
                m_monitor.cacheUserList(cacheKey, allUsers);
            }
        }
        return allUsers;
    }

    /**
     * Reads all resources that are inside and changed in a specified project.<p>
     *
     * @param dbc the current database context
     * @param projectId the ID of the project
     * @param mode one of the {@link CmsReadChangedProjectResourceMode} constants
     *
     * @return a List with all resources inside the specified project
     *
     * @throws CmsException if something goes wrong
     */
    private List<CmsResource> readChangedResourcesInsideProject(
        CmsDbContext dbc,
        CmsUUID projectId,
        CmsReadChangedProjectResourceMode mode)
    throws CmsException {

        String cacheKey = projectId + "_" + mode.toString();
        List<CmsResource> result = m_monitor.getCachedProjectResources(cacheKey);
        if (result != null) {
            return result;
        }
        List<String> projectResources = readProjectResources(dbc, readProject(dbc, projectId));
        result = new ArrayList<CmsResource>();
        String currentProjectResource = null;
        List<CmsResource> resources = new ArrayList<CmsResource>();
        CmsResource currentResource = null;
        CmsLock currentLock = null;

        for (int i = 0; i < projectResources.size(); i++) {
            // read all resources that are inside the project by visiting each project resource
            currentProjectResource = projectResources.get(i);

            try {
                currentResource = readResource(dbc, currentProjectResource, CmsResourceFilter.ALL);

                if (currentResource.isFolder()) {
                    resources.addAll(readResources(dbc, currentResource, CmsResourceFilter.ALL, true));
                } else {
                    resources.add(currentResource);
                }
            } catch (CmsException e) {
                // the project resource probably doesn't exist (anymore)...
                if (!(e instanceof CmsVfsResourceNotFoundException)) {
                    throw e;
                }
            }
        }

        for (int j = 0; j < resources.size(); j++) {
            currentResource = resources.get(j);
            currentLock = getLock(dbc, currentResource).getEditionLock();

            if (!currentResource.getState().isUnchanged()) {
                if ((currentLock.isNullLock() && (currentResource.getProjectLastModified().equals(projectId)))
                    || (currentLock.isOwnedBy(dbc.currentUser()) && (currentLock.getProjectId().equals(projectId)))) {
                    // add only resources that are
                    // - inside the project,
                    // - changed in the project,
                    // - either unlocked, or locked for the current user in the project
                    if ((mode == RCPRM_FILES_AND_FOLDERS_MODE)
                        || (currentResource.isFolder() && (mode == RCPRM_FOLDERS_ONLY_MODE))
                        || (currentResource.isFile() && (mode == RCPRM_FILES_ONLY_MODE))) {
                        result.add(currentResource);
                    }
                }
            }
        }

        resources.clear();
        resources = null;

        m_monitor.cacheProjectResources(cacheKey, result);
        return result;
    }

    /**
     * Sorts the given list of {@link CmsAccessControlEntry} objects.<p>
     *
     * The the 'all others' ace in first place, the 'overwrite all' ace in second.<p>
     *
     * @param aces the list of ACEs to sort
     *
     * @return <code>true</code> if the list contains the 'overwrite all' ace
     */
    private boolean sortAceList(List<CmsAccessControlEntry> aces) {

        // sort the list of entries
        Collections.sort(aces, CmsAccessControlEntry.COMPARATOR_ACE);
        // after sorting just the first 2 positions come in question
        for (int i = 0; i < Math.min(aces.size(), 2); i++) {
            CmsAccessControlEntry acEntry = aces.get(i);
            if (acEntry.getPrincipal().equals(CmsAccessControlEntry.PRINCIPAL_OVERWRITE_ALL_ID)) {
                return true;
            }
        }
        return false;
    }

    /**
     * All permissions and resources attributes of the principal
     * are transfered to a replacement principal.<p>
     *
     * @param dbc the current database context
     * @param project the current project
     * @param principalId the id of the principal to be replaced
     * @param replacementId the user to be transfered
     * @param withACEs flag to signal if the ACEs should also be transfered or just deleted
     *
     * @throws CmsException if operation was not successful
     */
    private void transferPrincipalResources(
        CmsDbContext dbc,
        CmsProject project,
        CmsUUID principalId,
        CmsUUID replacementId,
        boolean withACEs)
    throws CmsException {

        // get all resources for the given user including resources associated by ACEs or attributes
        I_CmsUserDriver userDriver = getUserDriver(dbc);
        I_CmsVfsDriver vfsDriver = getVfsDriver(dbc);
        Set<CmsResource> resources = getResourcesForPrincipal(dbc, project, principalId, null, true);
        Iterator<CmsResource> it = resources.iterator();
        while (it.hasNext()) {
            CmsResource resource = it.next();
            // check resource attributes
            boolean attrModified = false;
            CmsUUID createdUser = null;
            if (resource.getUserCreated().equals(principalId)) {
                createdUser = replacementId;
                attrModified = true;
            }
            CmsUUID lastModUser = null;
            if (resource.getUserLastModified().equals(principalId)) {
                lastModUser = replacementId;
                attrModified = true;
            }
            if (attrModified) {
                vfsDriver.transferResource(dbc, project, resource, createdUser, lastModUser);
                // clear the cache
                m_monitor.clearResourceCache();
            }
            boolean aceModified = false;
            // check aces
            if (withACEs) {
                Iterator<CmsAccessControlEntry> itAces = userDriver.readAccessControlEntries(
                    dbc,
                    project,
                    resource.getResourceId(),
                    false).iterator();
                while (itAces.hasNext()) {
                    CmsAccessControlEntry ace = itAces.next();
                    if (ace.getPrincipal().equals(principalId)) {
                        CmsAccessControlEntry newAce = new CmsAccessControlEntry(
                            ace.getResource(),
                            replacementId,
                            ace.getAllowedPermissions(),
                            ace.getDeniedPermissions(),
                            ace.getFlags());
                        // write the new ace
                        userDriver.writeAccessControlEntry(dbc, project, newAce);
                        aceModified = true;
                    }
                }
                if (aceModified) {
                    // clear the cache
                    m_monitor.clearAccessControlListCache();
                }
            }
            if (attrModified || aceModified) {
                // fire the event
                Map<String, Object> data = new HashMap<String, Object>(2);
                data.put(I_CmsEventListener.KEY_RESOURCE, resource);
                data.put(
                    I_CmsEventListener.KEY_CHANGE,
                    new Integer(((attrModified) ? CHANGED_RESOURCE : 0) | ((aceModified) ? CHANGED_ACCESSCONTROL : 0)));
                OpenCms.fireCmsEvent(new CmsEvent(I_CmsEventListener.EVENT_RESOURCE_MODIFIED, data));
            }
        }
    }

    /**
     * Undoes all content changes of a resource.<p>
     *
     * @param dbc the database context
     * @param onlineProject the online project
     * @param offlineResource the offline resource, or <code>null</code> if deleted
     * @param onlineResource the online resource
     * @param newState the new resource state
     * @param moveUndone is a move operation on the same resource has been made
     *
     * @throws CmsException if something goes wrong
     */
    private void undoContentChanges(
        CmsDbContext dbc,
        CmsProject onlineProject,
        CmsResource offlineResource,
        CmsResource onlineResource,
        CmsResourceState newState,
        boolean moveUndone)
    throws CmsException {

        String path = ((moveUndone || (offlineResource == null))
        ? onlineResource.getRootPath()
        : offlineResource.getRootPath());

        // change folder or file?
        I_CmsUserDriver userDriver = getUserDriver(dbc);
        I_CmsVfsDriver vfsDriver = getVfsDriver(dbc);
        if (onlineResource.isFolder()) {
            CmsFolder restoredFolder = new CmsFolder(
                onlineResource.getStructureId(),
                onlineResource.getResourceId(),
                path,
                onlineResource.getTypeId(),
                onlineResource.getFlags(),
                dbc.currentProject().getUuid(),
                newState,
                onlineResource.getDateCreated(),
                onlineResource.getUserCreated(),
                onlineResource.getDateLastModified(),
                onlineResource.getUserLastModified(),
                onlineResource.getDateReleased(),
                onlineResource.getDateExpired(),
                onlineResource.getVersion()); // version number does not matter since it will be computed later

            // write the folder in the offline project
            // this sets a flag so that the folder date is not set to the current time
            restoredFolder.setDateLastModified(onlineResource.getDateLastModified());

            // write the folder
            vfsDriver.writeResource(dbc, dbc.currentProject().getUuid(), restoredFolder, NOTHING_CHANGED);

            // restore the properties from the online project
            vfsDriver.deletePropertyObjects(
                dbc,
                dbc.currentProject().getUuid(),
                restoredFolder,
                CmsProperty.DELETE_OPTION_DELETE_STRUCTURE_AND_RESOURCE_VALUES);

            List<CmsProperty> propertyInfos = vfsDriver.readPropertyObjects(dbc, onlineProject, onlineResource);
            vfsDriver.writePropertyObjects(dbc, dbc.currentProject(), restoredFolder, propertyInfos);

            // restore the access control entries from the online project
            userDriver.removeAccessControlEntries(dbc, dbc.currentProject(), onlineResource.getResourceId());
            ListIterator<CmsAccessControlEntry> aceList = userDriver.readAccessControlEntries(
                dbc,
                onlineProject,
                onlineResource.getResourceId(),
                false).listIterator();

            while (aceList.hasNext()) {
                CmsAccessControlEntry ace = aceList.next();
                userDriver.createAccessControlEntry(
                    dbc,
                    dbc.currentProject(),
                    onlineResource.getResourceId(),
                    ace.getPrincipal(),
                    ace.getPermissions().getAllowedPermissions(),
                    ace.getPermissions().getDeniedPermissions(),
                    ace.getFlags());
            }
        } else {
            byte[] onlineContent = vfsDriver.readContent(
                dbc,
                CmsProject.ONLINE_PROJECT_ID,
                onlineResource.getResourceId());

            CmsFile restoredFile = new CmsFile(
                onlineResource.getStructureId(),
                onlineResource.getResourceId(),
                path,
                onlineResource.getTypeId(),
                onlineResource.getFlags(),
                dbc.currentProject().getUuid(),
                newState,
                onlineResource.getDateCreated(),
                onlineResource.getUserCreated(),
                onlineResource.getDateLastModified(),
                onlineResource.getUserLastModified(),
                onlineResource.getDateReleased(),
                onlineResource.getDateExpired(),
                0,
                onlineResource.getLength(),
                onlineResource.getDateContent(),
                onlineResource.getVersion(), // version number does not matter since it will be computed later
                onlineContent);

            // write the file in the offline project
            // this sets a flag so that the file date is not set to the current time
            restoredFile.setDateLastModified(onlineResource.getDateLastModified());

            // collect the old properties
            List<CmsProperty> properties = vfsDriver.readPropertyObjects(dbc, onlineProject, onlineResource);

            if (offlineResource != null) {
                // bug fix 1020: delete all properties (inclum_rejectStructureIdded shared),
                // shared properties will be recreated by the next call of #createResource(...)
                vfsDriver.deletePropertyObjects(
                    dbc,
                    dbc.currentProject().getUuid(),
                    onlineResource,
                    CmsProperty.DELETE_OPTION_DELETE_STRUCTURE_AND_RESOURCE_VALUES);

                // implementation notes:
                // undo changes can become complex e.g. if a resource was deleted, and then
                // another resource was copied over the deleted file as a sibling
                // therefore we must "clean" delete the offline resource, and then create
                // an new resource with the create method
                // note that this does NOT apply to folders, since a folder cannot be replaced
                // like a resource anyway
                deleteResource(dbc, offlineResource, CmsResource.DELETE_PRESERVE_SIBLINGS);
            }
            CmsResource res = createResource(
                dbc,
                restoredFile.getRootPath(),
                restoredFile,
                restoredFile.getContents(),
                properties,
                false);

            // copy the access control entries from the online project
            if (offlineResource != null) {
                userDriver.removeAccessControlEntries(dbc, dbc.currentProject(), onlineResource.getResourceId());
            }
            ListIterator<CmsAccessControlEntry> aceList = userDriver.readAccessControlEntries(
                dbc,
                onlineProject,
                onlineResource.getResourceId(),
                false).listIterator();

            while (aceList.hasNext()) {
                CmsAccessControlEntry ace = aceList.next();
                userDriver.createAccessControlEntry(
                    dbc,
                    dbc.currentProject(),
                    res.getResourceId(),
                    ace.getPrincipal(),
                    ace.getPermissions().getAllowedPermissions(),
                    ace.getPermissions().getDeniedPermissions(),
                    ace.getFlags());
            }

            vfsDriver.deleteUrlNameMappingEntries(
                dbc,
                false,
                CmsUrlNameMappingFilter.ALL.filterStructureId(res.getStructureId()).filterStates(
                    CmsUrlNameMappingEntry.MAPPING_STATUS_NEW,
                    CmsUrlNameMappingEntry.MAPPING_STATUS_REPLACE_ON_PUBLISH));
            // restore the state to unchanged
            res.setState(newState);
            m_vfsDriver.writeResourceState(dbc, dbc.currentProject(), res, UPDATE_ALL, false);
        }

        // delete all offline relations
        if (offlineResource != null) {
            vfsDriver.deleteRelations(dbc, dbc.currentProject().getUuid(), offlineResource, CmsRelationFilter.TARGETS);
        }
        // get online relations
        List<CmsRelation> relations = vfsDriver.readRelations(
            dbc,
            CmsProject.ONLINE_PROJECT_ID,
            onlineResource,
            CmsRelationFilter.TARGETS);
        // write offline relations
        Iterator<CmsRelation> itRelations = relations.iterator();
        while (itRelations.hasNext()) {
            CmsRelation relation = itRelations.next();
            vfsDriver.createRelation(dbc, dbc.currentProject().getUuid(), relation);
        }

        // update the cache
        m_monitor.clearResourceCache();
        m_monitor.flushCache(CmsMemoryMonitor.CacheType.PROPERTY, CmsMemoryMonitor.CacheType.PROPERTY_LIST);

        if ((offlineResource == null) || offlineResource.getRootPath().equals(onlineResource.getRootPath())) {
            log(
                dbc,
                new CmsLogEntry(
                    dbc,
                    onlineResource.getStructureId(),
                    CmsLogEntryType.RESOURCE_RESTORED,
                    new String[] {onlineResource.getRootPath()}),
                false);
        } else {
            log(
                dbc,
                new CmsLogEntry(
                    dbc,
                    offlineResource.getStructureId(),
                    CmsLogEntryType.RESOURCE_MOVE_RESTORED,
                    new String[] {offlineResource.getRootPath(), onlineResource.getRootPath()}),
                false);
        }
        if (offlineResource != null) {
            OpenCms.fireCmsEvent(
                new CmsEvent(
                    I_CmsEventListener.EVENT_RESOURCE_AND_PROPERTIES_MODIFIED,
                    Collections.<String, Object> singletonMap(I_CmsEventListener.KEY_RESOURCE, offlineResource)));
        } else {
            OpenCms.fireCmsEvent(
                new CmsEvent(
                    I_CmsEventListener.EVENT_RESOURCE_AND_PROPERTIES_MODIFIED,
                    Collections.<String, Object> singletonMap(I_CmsEventListener.KEY_RESOURCE, onlineResource)));
        }
    }

    /**
     * Updates the current users context dates with the given resource.<p>
     *
     * This checks the date information of the resource based on
     * {@link CmsResource#getDateLastModified()} as well as
     * {@link CmsResource#getDateReleased()} and {@link CmsResource#getDateExpired()}.
     * The current users request context is updated with the the "latest" dates found.<p>
     *
     * This is required in order to ensure proper setting of <code>"last-modified"</code> http headers
     * and also for expiration of cached elements in the Flex cache.
     * Consider the following use case: Page A is generated from resources x, y and z.
     * If either x, y or z has an expiration / release date set, then page A must expire at a certain point
     * in time. This is ensured by the context date check here.<p>
     *
     * @param dbc the current database context
     * @param resource the resource to get the date information from
     */
    private void updateContextDates(CmsDbContext dbc, CmsResource resource) {

        CmsFlexRequestContextInfo info = dbc.getFlexRequestContextInfo();
        if (info != null) {
            info.updateFromResource(resource);
        }
    }

    /**
     * Updates the current users context dates with each {@link CmsResource} object in the given list.<p>
     *
     * The given input list is returned unmodified.<p>
     *
     * Please see {@link #updateContextDates(CmsDbContext, CmsResource)} for an explanation of what this method does.<p>
     *
     * @param dbc the current database context
     * @param resourceList a list of {@link CmsResource} objects
     *
     * @return the original list of CmsResources with the full resource name set
     */
    private List<CmsResource> updateContextDates(CmsDbContext dbc, List<CmsResource> resourceList) {

        CmsFlexRequestContextInfo info = dbc.getFlexRequestContextInfo();
        if (info != null) {
            for (int i = 0; i < resourceList.size(); i++) {
                CmsResource resource = resourceList.get(i);
                info.updateFromResource(resource);
            }
        }
        return resourceList;
    }

    /**
     * Returns a List of {@link CmsResource} objects generated when applying the given filter to the given list,
     * also updates the current users context dates with each {@link CmsResource} object in the given list,
     * also applies the selected resource filter to all resources in the list and returns the remaining resources.<p>
     *
     * Please see {@link #updateContextDates(CmsDbContext, CmsResource)} for an explanation of what this method does.<p>
     *
     * @param dbc the current database context
     * @param resourceList a list of {@link CmsResource} objects
     * @param filter the resource filter to use
     *
     * @return a List of {@link CmsResource} objects generated when applying the given filter to the given list
     */
    private List<CmsResource> updateContextDates(
        CmsDbContext dbc,
        List<CmsResource> resourceList,
        CmsResourceFilter filter) {

        if (CmsResourceFilter.ALL == filter) {
            // if there is no filter required, then use the simpler method that does not apply the filter
            return new ArrayList<CmsResource>(updateContextDates(dbc, resourceList));
        }

        CmsFlexRequestContextInfo info = dbc.getFlexRequestContextInfo();
        List<CmsResource> result = new ArrayList<CmsResource>(resourceList.size());
        for (int i = 0; i < resourceList.size(); i++) {
            CmsResource resource = resourceList.get(i);
            if (filter.isValid(dbc.getRequestContext(), resource)) {
                result.add(resource);
            }
            // must also include "invalid" resources for the update of context dates
            // since a resource may be invalid because of release / expiration date
            if (info != null) {
                info.updateFromResource(resource);
            }
        }
        return result;
    }

    /**
     * Updates the state of a resource, depending on the <code>resourceState</code> parameter.<p>
     *
     * @param dbc the db context
     * @param resource the resource
     * @param resourceState if <code>true</code> the resource state will be updated, if not just the structure state.
     *
     * @throws CmsDataAccessException if something goes wrong
     */
    private void updateState(CmsDbContext dbc, CmsResource resource, boolean resourceState)
    throws CmsDataAccessException {

        CmsUUID projectId = ((dbc.getProjectId() == null) || dbc.getProjectId().isNullUUID())
        ? dbc.currentProject().getUuid()
        : dbc.getProjectId();
        resource.setUserLastModified(dbc.currentUser().getId());
        if (resourceState) {
            // update the whole resource state
            getVfsDriver(dbc).writeResource(dbc, projectId, resource, UPDATE_RESOURCE_STATE);
        } else {
            // update the structure state
            getVfsDriver(dbc).writeResource(dbc, projectId, resource, UPDATE_STRUCTURE_STATE);
        }
    }

    /**
     * Wraps a driver object with a dynamic proxy that counts method calls and their durations.<p>
     *
     * @param newDriverInstance the driver instance to wrap
     * @return the proxy
     */
    private Object wrapDriverInProfilingProxy(Object newDriverInstance) {

        Class<?> cls = getDriverInterfaceForProxy(newDriverInstance);
        if (cls == null) {
            return newDriverInstance;
        }
        return Proxy.newProxyInstance(
            Thread.currentThread().getContextClassLoader(),
            new Class[] {cls},
            new CmsProfilingInvocationHandler(newDriverInstance, CmsDefaultProfilingHandler.INSTANCE));
    }

}
